Application uses GIDSignIn and Firebase for google authentication in my iOS app.
I am trying to add additional scopes to the authentication flow, however, I do not know the proper way to add the needed scopes.
Google Sign in Documentation
func signInWithGoogle() {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
// Start the sign in flow!
GIDSignIn.sharedInstance.signIn(with: config, presenting: self) { [unowned self] user, error in
if let error = error {
// ...
return
}
guard
let authentication = user?.authentication,
let idToken = authentication.idToken
else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken,
accessToken: authentication.accessToken)
// ...
Auth.auth().signIn(with: credential, completion: { (user, error) in
if let err = error {
print("Failed to create a Firebase User with Google account: ", err)
return
}
// Successfully logged in
guard let uid = user?.user.uid else { return }
print("Successfully logged into Firebase with Google", uid)
let mainTabBarController = TabBarViewController()
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.changeRootViewController(mainTabBarController)
})
}
}
I need to add the code below to add scopes to the authentication process. I just dont know how to properly ask for the scopes without interrupting the firebase login process.
let additionalScopes = ["https://www.googleapis.com/auth/youtube.readonly", "https://www.googleapis.com/auth/yt-analytics.readonly"]
GIDSignIn.sharedInstance.addScopes(additionalScopes, presenting: self) { user, error in
guard error == nil else { return }
guard let user = user else { return }
// Check if the user granted access to the scopes you requested.
}
A similar post with a similar problem can be found here
Related
Hi all I was trying to use sign in with google and firebase in a SwiftUI project. Now checking the old implementation methods and also some suggestions got from the net I am having problem with this part of the code
private func authenticateUser(for user: GIDGoogleUser?, with error: Error?) {
// 1
if let error = error {
print(error.localizedDescription)
return
}
// 2
guard let authentication = user?.authentication, let idToken = authentication.idToken else { return }
let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: authentication.accessToken)
// 3
Auth.auth().signIn(with: credential) { [unowned self] (_, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.state = .signedIn
}
}
}
I'm getting errors with this authentication constant
guard let authentication = user?.authentication, let idToken = authentication.idToken else { return }
The error is Value of type 'GIDGoogleUser' has no member 'autentication'
i know google dropped some properties replacing them...currently how can i update the google login implementation in SwiftUI?
Here is how you can authenticate a Firebase user when using Google Sign-In using the latest release (7.0.0) of the Google Sign-In SDK:
extension AuthenticationViewModel {
func signInWithGoogle() async -> Bool {
guard let clientID = FirebaseApp.app()?.options.clientID else {
fatalError("No client ID found in Firebase configuration")
}
let config = GIDConfiguration(clientID: clientID)
GIDSignIn.sharedInstance.configuration = config
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first,
let rootViewController = window.rootViewController else {
print("There is no root view controller!")
return false
}
do {
let userAuthentication = try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController)
let user = userAuthentication.user
guard let idToken = user.idToken else { throw AuthenticationError.tokenError(message: "ID token missing") }
let accessToken = user.accessToken
let credential = GoogleAuthProvider.credential(withIDToken: idToken.tokenString,
accessToken: accessToken.tokenString)
let result = try await Auth.auth().signIn(with: credential)
let firebaseUser = result.user
print("User \(firebaseUser.uid) signed in with email \(firebaseUser.email ?? "unknown")")
return true
}
catch {
print(error.localizedDescription)
self.errorMessage = error.localizedDescription
return false
}
}
}
This code is part of a video I am currently working on, and I will update this answer with more details, but for the time being this might help you out.
I've been trying to integrate google sign into my ios project but as they have updated their API I Can't find the solution as everyone is using GIDSignIn.sharedInstance()?.delegate and it doesn't exist anymore.
Documentation
so what I understood from the documentation is:
after setting up GoogleSignIn dependency->
added URL Schemes.
Implement the application:openURL:options: method of your app delegate.
#available(iOS 9.0, *)
func application(_ application: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any])
-> Bool {
return GIDSignIn.sharedInstance.handle(url)
}
Added a view in Storyboard and assigned class GIDSignInButton.
4.Pass the presenting view controller and client ID for your app to the Google Sign In sign-in method and create a Firebase auth
credential from the resulting Google auth token: (documentation)
(not quite sure where to add this, Right now I am adding it to the ViewDidLoad method of my loginViewController , without changing anything)
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
// Start the sign in flow!
GIDSignIn.sharedInstance.signIn(with: config, presenting: self) { [unowned self] user, error in
if let error = error {
// ...
return
}
guard
let authentication = user?.authentication,
let idToken = authentication.idToken
else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: idToken,
accessToken: authentication.accessToken)
// ..AuthCode.
}
5. Then finally adding authentication below credential :
Auth.auth().signIn(with: credential) { authResult, error in
if let error = error {
let authError = error as NSError
if isMFAEnabled, authError.code == AuthErrorCode.secondFactorRequired.rawValue {
// The user is a multi-factor user. Second factor challenge is required.
let resolver = authError
.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
var displayNameString = ""
for tmpFactorInfo in resolver.hints {
displayNameString += tmpFactorInfo.displayName ?? ""
displayNameString += " "
}
self.showTextInputPrompt(
withMessage: "Select factor to sign in\n\(displayNameString)",
completionBlock: { userPressedOK, displayName in
var selectedHint: PhoneMultiFactorInfo?
for tmpFactorInfo in resolver.hints {
if displayName == tmpFactorInfo.displayName {
selectedHint = tmpFactorInfo as? PhoneMultiFactorInfo
}
}
PhoneAuthProvider.provider()
.verifyPhoneNumber(with: selectedHint!, uiDelegate: nil,
multiFactorSession: resolver
.session) { verificationID, error in
if error != nil {
print(
"Multi factor start sign in failed. Error: \(error.debugDescription)"
)
} else {
self.showTextInputPrompt(
withMessage: "Verification code for \(selectedHint?.displayName ?? "")",
completionBlock: { userPressedOK, verificationCode in
let credential: PhoneAuthCredential? = PhoneAuthProvider.provider()
.credential(withVerificationID: verificationID!,
verificationCode: verificationCode!)
let assertion: MultiFactorAssertion? = PhoneMultiFactorGenerator
.assertion(with: credential!)
resolver.resolveSignIn(with: assertion!) { authResult, error in
if error != nil {
print(
"Multi factor finanlize sign in failed. Error: \(error.debugDescription)"
)
} else {
self.navigationController?.popViewController(animated: true)
}
}
}
)
}
}
}
)
} else {
self.showMessagePrompt(error.localizedDescription)
return
}
// ...
return
}
// User is signed in
// ...
}
The auth code is taken from the documentation, it's not updated and surely I won't need this many details just UId.
I don't know what the problem is but it doesn't seem to work. On running, just Google sign-in button appears and on clicking it nothing happens
It appears that there is some problem with GIDSignInButton. Made A custom button and it is working fine
I can't login and always see this error message:
The verification ID used to create the phone auth credential is invalid
I tried to add my phone like tester, but I wasn't succeed on it.
Sign in method is turn on in Firebase console.
I found the same issue here, but without answer.
I used firebase's docs.
Help me please to figure it out.
Here my code:
func verifyPhone(_ phone: String) -> AsyncTask<String?> {
return AsyncTask<String?>({ observer, lifetime in
guard !lifetime.hasEnded else {
observer.sendInterrupted()
return
}
Auth.auth().languageCode = "ua"
PhoneAuthProvider.provider().verifyPhoneNumber(phone, uiDelegate: nil) { verificationID, error in
if let error = error {
observer.send(error: AppError(error))
return
}
observer.send(value: verificationID)
observer.sendCompleted()
}
})
}
func signInViaPhoneNumber(usingCode smsCode: String) -> AsyncTask<Void> {
return AsyncTask<Void>({ observer, lifetime in
guard !lifetime.hasEnded else {
observer.sendInterrupted()
return
}
guard let verificationCode = UserDefaultsStorage.verificationCode else {
observer.send(error: AppError.logic("Відсутній код верифікації"))
return
}
let credential = PhoneAuthProvider.provider().credential(withVerificationID: smsCode,
verificationCode: verificationCode)
Auth.auth().signIn(with: credential) { result, error in
if let error = error {
observer.send(error: AppError(error))
return
}
guard let firUser = result?.user else {
observer.send(error: AppError.logic("Відсутній юзер"))
return
}
let factory: ModelFactory = ModelFactoryImpl()
let appUser = factory.makeUser(id: firUser.uid, name: firUser.displayName ?? "", phone: firUser.phoneNumber ?? "")
AppService.shared.user = appUser
observer.send(value: ())
observer.sendCompleted()
}
})
}
It's hard for me to see the complete code flow because you are using AsyncTask and delegating all the results outside.
The issue that pops out in your code is the line
PhoneAuthProvider.provider().credential(withVerificationID: smsCode,
verificationCode: verificationCode)
In this method verificationCode should be the code you received via SMS and withVerificationID should be the verificationId that you got in the verifyPhoneNumber callback.
Using email when logging in a new user or creating a new user there are 2 different method signatures. When creating a new user if the email already exists an error will be returned or logging a user in if the email doesn't exist an error will be returned:
// create account
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: { (authDataResult, error)
if let error = error {
// if this email address already exists an error will be returned
return
}
})
// login
Auth.auth().signIn(withEmail: emailTextField.text!, password: self.passwordTextField.text!, completion: { (authDataResult, error) in
if let error = error {
// if this email address isn't inside the system then an error will be returned
return
}
})
But when using a user's phone number to log them is or create a new account I have to use the same method signature for both situations.
func loginExistingUserOrCreateNewOne(phoneNumber: String, verificationCode: String) {
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
if let error = error { return }
guard let verificationId = verificationID else { return }
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationId, verificationCode: verificationCode)
Auth.auth().signIn(with: credential, completion: { (authDataResult, error) in
guard let authUser = authDataResult else { return }
let checkUsersRef = Database.database().reference().child("users").child(authUser.user.uid)
checkExistingUsersRef.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
// this is a new user, now add them to the users ref
let newUserDict = ["signupDate": Date().timeIntervalSince1970]
checkUsersRef.updateChildValues(newUserDict, withCompletionBlock: { (error, ref) in
if let error = error {
// because there is an error this ref was never updated so now I have to sign this user out and they have to start over agin
do {
try Auth.auth().signOut()
} catch let err as NSError {
// alert user there is a major problem
}
return
}
// if no error let them go to HomeVC
})
return
}
// this is a previous user fetch dict data and let them proceed to HomeVC
guard let previousUserDict = snapshot.value as? [String: Any] else { return }
// get newUserDict values and let them go to HomeVC
})
})
}
}
If a user already has an account I need to fetch some data from the users ref and then I let them proceed to HomeVC. If the user has never signed up before then I have to add them to the users ref and then let them proceed. It's a 2 step process.
The problem is these extra steps seems unnecessary. For example using email sign or login an error is returned so there is no need to create and check inside another ref to see if that email already exists.
Outside of using the process in my above code is there any other way that I can determine if a phone number exists before creating a new account or if it doesn't exist when logging in?
You will need to use the admin sdk to lookup a user by phone number:
admin.auth().getUserByPhoneNumber(phoneNumber)
.then(function(userRecord) {
// User exists.
})
.catch(function(error) {
if (error.code === 'auth/user-not-found') {
// User not found.
}
});
You can use a Cloud Function to host an HTTP endpoint. Looking up a user by phone number is only possible via authenticated APIs running server side (using the Firebase Admin SDKs).
I wish to re-authenticate a user prior to allowing them to change their login information. However, due to the recent Firebase update, I found the documentation rather unhelpful. Using this link I produced the following authenticateUser() function.
func authenticateUser()
{
let user = FIRAuth.auth()?.currentUser
var credential: FIRAuthCredential
//prompt user to re-enter info
user?.reauthenticateWithCredential(credential, completion: { (error) in
if error != nil
{
self.displayAlertMessage("Error reauthenticating user")
}
else
{
//user reauthenticated successfully
}
})
}
However, I am unsure what to do with the credential variable of type FIRAuthCredential, in order to re-authenticate the user. The documentation for this class can be found here.
Getting the FIRAuthCredential object depends on what provider you want to use to reauthenticate.
Email:
let credential = EmailAuthProvider.credential(withEmail: email, password: password)
Facebook:
let credential = FacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.currentAccessToken().tokenString)
Twitter:
let credential = TwitterAuthProvider.credential(withToken: session.authToken, secret: session.authTokenSecret)
Google:
let authentication = user.authentication
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken)
In Swift 4 and latest firebase 4 the names have changed a bit, but the principle still remains. For your convenience:
let eMail = EmailAuthProvider.credential(withEmail: "some#email.com", password: "somepassword")
let fb = FacebookAuthProvider.credential(withAccessToken: "xxx")
let g = GoogleAuthProvider.credential(withIDToken: "xxx", accessToken: "xxx")
...
Auth.auth().currentUser?.reauthenticate(with: eMail, completion: {
[weak self]
(error) in
...
})
Firebase's documentation is currently outdated. Here is the correct way to handle reauthenticate.
let user = Auth.auth().currentUser
user?.reauthenticate(with: credential, completion: { (result, error) in
if let err = error {
//..read error message
} else {
//.. go on
}
})