Hi I am a bit new to iOS development. I want to create a variable that store the UID = user.id from the firebase authentication check. Then I want to use that UID to put it in the url so that I can use that to call our backend. However, I couldn't figure out how to do it...
Xcode 11.6
swift 5
here is my swift code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var UID = ""
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.userName.text = user.displayName
//print(user.uid)
UID = user.uid
} else {
self.userName.text = "error"
}
}
self.navigationItem.setHidesBackButton(true, animated: true)
//let url = vehicleManager.dataURL
let url = rewardManager.dataURL
print("UID is: \(UID)")
rewardManager.performRequest(dataURL: url)
claimWebKitView.load(URLRequest(url: URL(string: "https://camo.githubusercontent.com/6b254aa699df7f9464967009129c3017de721b77/68747470733a2f2f7261772e6769746875622e636f6d2f5068696c4a61792f4d50416e64726f696443686172742f6d61737465722f73637265656e73686f74732f7363617474657263686172742e706e67")!))
premiumWebKitView.load(URLRequest(url: URL(string: "https://camo.githubusercontent.com/e29d8d3316203700965cc6cc56e67b779f2845bb/68747470733a2f2f7261772e6769746875622e636f6d2f5068696c4a61792f4d5043686172742f6d61737465722f73637265656e73686f74732f636f6d62696e65645f63686172742e706e67")!))
}
debug console:
how can I pass the user.uid to my url? thanks
The reason why you are not getting any value in UID while printing is because the auth.addStateChangedListener is an async operation that is it takes some time to get your data from firebase and in that time duration your other code gets executed first. I would suggest to use completion handlers for these type of async operations. Here is the link of S.O question from where you can get more details about completion handlers.
Quick Overview :- Your completion handler gets called when the function has completed it's execution and at that part you can do your api calls by checking if you have received the correct userId or not.
after the great help from #Dev App and others, I finally figured it out I think.
So the key is to call my API in side this Firebase authentication method
func getUserName() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.userName.text = user.displayName
} else {
self.userName.text = "error"
}
}
}
any data in that closure, such as user.displayname or user.uid is only valid in that closure.
So, I added my callApi function into that firebase authentication function. Something like below solves the problem.
func callApiFunction() {
//some code to call the api
}
func firebaseAuth() {
// some code to do authentication, and get back some data, for example:
func getUserName() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
self.userName.text = user.uid
// you can pass the uid here to the callApiFunction()
callApiFunction()
} else {
self.userName.text = "error"
}
}
}
}
then you can call the func firebaseAuth() in other places, like viewdidLoad() etc.
override func viewDidLoad() {
firebaseAuth()
}
Related
i'm using this pod to use Google reCaptcha when users authenticate to an iOS app.
The captcha is shown, but not clickable.
in my loginViewController:
private var recaptcha: ReCaptcha?
private var locale: Locale = Locale(identifier: "fr-FR")
private var endpoint = ReCaptcha.Endpoint.default
override func viewDidLoad() {
super.viewDidLoad()
do {
recaptcha = try ReCaptcha(apiKey: Config.API_KEY, baseURL: URL(string: "http://localhost"), endpoint: .default, locale: locale)
}
catch {
print("error")
}
recaptcha?.configureWebView { [weak self] webview in
webview.frame = self?.view.bounds ?? CGRect.zero
}
recaptcha?.forceVisibleChallenge = true
}
func validate(){
recaptcha?.validate(on: view, resetOnError: true, completion: { (ReCaptchaResult) in
switch ReCaptchaResult {
case .error:
print("error")
case .token:
//WHAT TO DO WITH THE TOKEN HERE?
guard let userName = self.userNameTextField.text, let password = self.passwordTextField.text else {
return
}
self.authentificationPresenter.authenticate(userName: userName, password: password)
}
})
}
#IBAction func loginButtonTapped() {
validate()
}
The result is this. I don't understand how to go from here, and interpret the user's result.
Since the Captcha is loaded in a webview and to my knowledge would not be directly displayed on a ViewController is it possible that your Captcha is is somehow covered by an empty view? This may be why it is not "clickable". Either that or perhaps you need to enable interactions inside of your webview - you can try adding this when you configure the web view:
webview.isUserIntractionEnabled = true
As far as interpreting the result, if the user successfully completes the Captcha test you should receive a client token which you will use alongside your API secret key in a post request to the endpoint provided. Here is the documentation for verifying the user following the completion of a Captcha test: https://developers.google.com/recaptcha/docs/verify. Hope this helped.
I want user to login once and not have to reenter their login info everytime they open app unless they logout in the last session.
Login screen is currently displayed everytime the app is open. This is my rootview
struct AppRootView: View {
var body: some View {
AnyView {
// check if user has already logged in here and then route them accordingly
if auth.token != nil {
homeMainView()
} else {
LoginController()
}
}
}
}
currently this is what I use to login users
#objc func signUp() {
setLoading(true);
app.usernamePasswordProviderClient().registerEmail(username!, password: password!, completion: {[weak self](error) in
// Completion handlers are not necessarily called on the UI thread.
// This call to DispatchQueue.main.sync ensures that any changes to the UI,
// namely disabling the loading indicator and navigating to the next page,
// are handled on the UI thread:
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
print("Signup failed: \(error!)")
self!.errorLabel.text = "Signup failed: \(error!.localizedDescription)"
return
}
print("Signup successful!")
// Registering just registers. Now we need to sign in, but we can reuse the existing username and password.
self!.errorLabel.text = "Signup successful! Signing in..."
self!.signIn()
}
})
}
#objc func signIn() {
print("Log in as user: \(username!)");
setLoading(true);
app.login(withCredential: AppCredentials(username: username!, password: password!)) { [weak self](maybeUser, error) in
DispatchQueue.main.sync {
self!.setLoading(false);
guard error == nil else {
// Auth error: user already exists? Try logging in as that user.
print("Login failed: \(error!)");
self!.errorLabel.text = "Login failed: \(error!.localizedDescription)"
return
}
guard let user = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
//
let hostingController = UIHostingController(rootView: ContentView())
self?.navigationController?.pushViewController(hostingController, animated: true)
}
how could I implement one time login so that users do have to login each time they open the app?
A correctly configured and initialized RealmApp class will persist the session information for you between app restarts, you can check for an existing session using the .currentUser() method from this class. So in your case something like:
if app.currentUser() != nil {
homeMainView()
} else {
LoginController()
}
While using Realm to persist login is a good idea, but I would highly
advice against using it for managing user authentication credentials such
as passwords. A better approach if you want to save sensitive information is
using KeyChain just like what Apple and password manager apps do. With a light
weight keyChain wrapper library such as SwiftKeychainWrapper You can easily
save your login credentials in the most secure way.
Here is a sample using a keyChain wrapper linked above.
With simple modification you can use this helper class to manage your sign in credentials anywhere in your app.
import SwiftKeychainWrapper
class KeyChainService {
// Make a singleton
static let shared = KeyChainService()
// Strings which will be used to map data in keychain
private let passwordKey = "passwordKey"
private let emailKey = "emailKey"
private let signInTokenKey = "signInTokenKey"
// Saving sign in info to keyChain
func saveUserSignInInformation(
email: String,
password: String,
token: String
onError: #escaping() -> Void,
onSuccess: #escaping() -> Void
) {
DispatchQueue.global(qos: .default).async {
let passwordIsSaved: Bool = KeychainWrapper.standard.set(password, forKey: self.passwordKey)
let emailIsSaved: Bool = KeychainWrapper.standard.set(email, forKey: self.emailKey)
let tokenIsSaved: Bool = KeychainWrapper.standard.set(token, forKey: self.signInTokenKey)
DispatchQueue.main.async {
// Verify that everything is saved as expected.
if passwordIsSaved && emailIsSaved && tokenIsSaved {
onSuccess()
}else {
onError()
}
}
}
}
// Retrieve signIn information for auto login
func retrieveSignInInfo(onError: #escaping() -> Void, onSuccess: #escaping(UserModel) -> Void) {
DispatchQueue.main.async {
let retrievedPassword: String? = KeychainWrapper.standard.string(forKey: self.passwordKey)
let retrievedEmail: String? = KeychainWrapper.standard.string(forKey: self.emailKey)
let retrievedToken: String? = KeychainWrapper.standard.string(forKey: self.signInTokenKey)
if let password = retrievedPassword,
let email = retrievedEmail,
let token = retrievedToken {
// Assuming that you have a custom user model named "UserModel"
let user = UserModel(email: email, password: password,token: token)
// Here is your user info which you can use to verify with server if needed and auto login user.
onSuccess(user)
}else {
onError()
}
}
}
}
I have created a very basic sign in app in swift to practice firebase. I've come up with this:
#IBAction func signInPressed(_ sender: UIButton) {
//Assigns and checks if the email and password aren't empty
if let inpt_email = emailField.text, let inpt_password = passwordField.text {
Auth.auth().signIn(withEmail: inpt_email, password: inpt_password, completion: { (user, error) in
//Checks if the user exists
if error != nil {
//ERROR: No user found
self.signInLabel.text = "Invalid User! Please Try Again"
} else {
//Sign Success
self.performSegue(withIdentifier: "toHome", sender: self)
}
})
}
} //End of signInPressed
// END: SIGN IN BUTTON
The //Sign Success part doesn't actually get any data at all, it just checks if the input matches any User that is registered in Firebase, and then segue to the next page. What I want to do is to:
Get the uid of the user which matches both the emailField and passwordField in the Firebase Auth.
Somewhat registers that uid as "Currently Signed In" in the app itself for future reference.
I tried reading the Firebase Documentation and all I got was this:
if Auth.auth().currentUser != nil {
// User is signed in.
// ...
} else {
// No user is signed in.
// ...
}
And this:
let user = Auth.auth().currentUser
if let user = user {
// The user's ID, unique to the Firebase project.
// Do NOT use this value to authenticate with your backend server,
// if you have one. Use getTokenWithCompletion:completion: instead.
let uid = user.uid
let email = user.email
let photoURL = user.photoURL
// ...
}
I'm new to Firebase so I basically don't understand how to use this, although I kind of get what it means, I just don't know what it's for or how to put it in action.
Thanks!
try this
if let user = user {
print(user.uid)
}
self.performSegue(withIdentifier: "toHome", sender: self)
I wanna check if the user has still a valid session, before I present the Home View controller of my app. I use the latest Firebase API. I think if I use the legacy, I'll be able to know this.
Here's what I did so far:
I posted my question on Slack community of Firebase, no one is answering. I found this one, but this is for Android: https://groups.google.com/forum/?hl=el#!topic/firebase-talk/4HdhDvVRqHc
I tried reading the docs of Firebase for iOS, but I can't seem to comprehend it: https://firebase.google.com/docs/reference/ios/firebaseauth/interface_f_i_r_auth
I tried typing in Xcode like this:
FIRApp().currentUser()
FIRUser().getCurrentUser()
But I can't seem to find that getCurrentUser function.
if FIRAuth.auth().currentUser != nil {
presentHome()
} else {
//User Not logged in
}
For updated SDK
if Auth.auth().currentUser != nil {
}
Updated answer
Solution for latest Firebase SDK - DOCS
// save a ref to the handler
private var authListener: AuthStateDidChangeListenerHandle?
// Check for auth status some where
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
authListener = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// User is signed in
// let the user in?
if user.isEmailVerified {
// Optional - check if the user verified their email too
// let the user in?
}
} else {
// No user
}
}
}
// Remove the listener once it's no longer needed
deinit {
if let listener = authListener {
Auth.auth().removeStateDidChangeListener(authListener)
}
}
Original solution
Solution in Swift 3
override func viewDidLoad() {
super.viewDidLoad()
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
if user != nil {
self.switchStoryboard()
}
}
}
Where switchStoryboard() is
func switchStoryboard() {
let storyboard = UIStoryboard(name: "NameOfStoryboard", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "ViewControllerName") as UIViewController
self.present(controller, animated: true, completion: nil)
}
Source
Solution in Swift 4
override func viewDidLoad() {
super.viewDidLoad()
setupLoadingControllerUI()
checkIfUserIsSignedIn()
}
private func checkIfUserIsSignedIn() {
Auth.auth().addStateDidChangeListener { (auth, user) in
if user != nil {
// user is signed in
// go to feature controller
} else {
// user is not signed in
// go to login controller
}
}
}
if Auth.auth().currentUser?.uid != nil {
//user is logged in
}else{
//user is not logged in
}
While you can see if there is such a user using Auth.auth().currentUser, this will only be telling you if there was a user authenticated, regardless of whether that users account still exists or is valid.
Complete Solution
The real solution to this should be using Firebase's re-authentication:
open func reauthenticate(with credential: AuthCredential, completion: UserProfileChangeCallback? = nil)
This assures (upon the launch of the application) that the previously signed in / authenticated user still in fact is and can be authenticated through Firebase.
let user = Auth.auth().currentUser // Get the previously stored current user
var credential: AuthCredential
user?.reauthenticate(with: credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
override func viewDidLoad() {
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
// 2
if user != nil {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "Home")
self.present(vc!, animated: true, completion: nil)
}
}
}
Source: https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
An objective-c solution would be (iOS 11.4):
[FIRAuth.auth addAuthStateDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) {
if (user != nil) {
// your logic
}
}];
All the provided answers only check on currentUser. But you could check the auth session by simple user reload like below:
// Run on the background thread since this is just a Firestore user reload, But you could also directly run on the main thread.
DispatchQueue.global(qos: .background).async {
Auth.auth().currentUser?.reload(completion: { error in
if error != nil {
DispatchQueue.main.async {
// Authentication Error
// Do the required work on the main thread if necessary
}
} else {
log.info("User authentication successfull!")
}
})
}
I'm trying to create an authentication page in storyboard using IOS swift and Firebase. Here is my storyboardsegue declaration:
Then I'm using shouldPerformSegueWithIdentifier to authenticate it. However, I also what to log the user in Firebase so my code is like so:
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if db.authData != nil {
return true
} else {
let email = emailTextField.text
let password = passwordTextField.text
db.authUser(email, password: password, withCompletionBlock: {
error, authData in
if error != nil {
print(error.description)
} else {
print("logged in")
}
})
return false
}
}
This code somewhat works but I would have to click the log in button twice because the first time logs in the user in Firebase, and the second time the db.authData is no longer nil because the user is already logged in so it returns true. I don't want to have to click twice to log in, I just want to click once. I can't just put return true or return false in the withCompletionBlock either because the block returns void. How do I make this work?
Using current implementation you can't achieve this feature. You have two ways to do this:
Make the db.authUser() synchronous and return it's result
Instead of connecting the login button segue to next screen, add an IBAction method and implement the method like
#IBAction func login(sender : AnyObject?)
{
let email = emailTextField.text
let password = passwordTextField.text
db.authUser(email, password: password, withCompletionBlock: {
error, authData in
if error != nil
{
print(error.description)
}
else
{
// Navigate to next screen
// Start perform segue here
}
})
}