Swift iOS GIDSignIn scopes are not auto selected - ios

iOS app using GIDSignIn for o-Authentication.
We are wondering why when the scopes are presented and not auto selected
Our Client ID and app is verified in our google console and we do NOT show an unsafe app upon sign in.
Does this change when the app is live in the app store? We do not understand why we have to select the scope ourselves when in
func signInWithGoogle() {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
print("Client ID: \(clientID)")
let additionalScopes = ["https://www.googleapis.com/auth/youtube.readonly", "https://www.googleapis.com/auth/yt-analytics.readonly"]
// Start the sign in flow! GIDSignIn
GIDSignIn.sharedInstance.signIn(with: config, presenting: self, hint: nil, additionalScopes: additionalScopes) { [unowned self] user, error in
if let error = error {
// ...
return
}
}
GIDGoogleSignIn Framework Reference

"This is the intended behavior. We require the user to provide explicit consent for each scope being requested in addition to basic profile.
Note that you can check which scopes the user grants after a successful sign-in and, at an appropriate moment, re-request the additional scopes via
addScopes
if needed."
GoogleSignIn-iOS Github Source

Related

How to get FBSDKAccessToken from FBSDKAuthenticationToken

In case of LAT FB login, we are just getting FBSDKAuthenticationToken and FBSDKAccessToken is nil.
https://developers.facebook.com/docs/facebook-login/limited-login/ios/
FBSDK Login is now using the Advertiser tracking flag(user consent)for iOS 14.
How to get FBSDKAccessToken from FBSDKAuthenticationToken? Or how to get FB profile from FBSDKAuthenticationToken?
According to this blog post of Facebook, limited mode and classic mode use different authentication methods behind.
Limited Login mode is based on the OpenID Connect standard.
Classic Login mode utilizes oAuth 2.0.
Therefore, I think there's no way to get access token by authentication token.
But we can get basic profile of user under limited login mode. There's a code snippet from Facebook's documentation reveals how to do that.
let loginManager = LoginManager()
// Ensure the configuration object is valid
guard let configuration = LoginConfiguration(tracking: .limited, nonce: "123")
else {
return
}
loginManager.logIn(configuration: configuration) { result in
switch result {
case .cancelled, .failed:
// Handle error
break
case .success:
// getting user ID
let userID = Profile.current?.userID
// getting pre-populated email
let email = Profile.current?.email
// getting id token string
let tokenString = AuthenticationToken.current?.tokenString
}
}
Also note that limited login mode does not support Graph API.

How to figure out "Sign in with Apple" in user with multiple devices with same apple id?

Here is my scenario.
Someone has iPhone and iPad (iOS 13.0 or later), and he signed up with same appleID.
And he `sign in with apple` in his iPhone and try to `sign in with apple` again in his iPad.
I don't want him to "sign in with apple" twice by editing his name or checking share or hide his email twice when he touch signIn Button.
I found out some codes from sample code that can help my scenario above
func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]
// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
And I found out ASPasswordCredential will help me define credential user
First, should I implement saving userIdentifier in iCloud Keychain? Then should I need to add Keychain group Capability?
First, I don't really know where to put that performExistingAccountSetupFlows function in. I would like to present AuthorizationController when user touch button. So I tried this approach.
#available(iOS 13.0, *)
#objc private func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
appleIDProvider.getCredentialState(
forUserID: KeychainItem.currentUserIdentifier ?? "") { [weak self] (credentialState, error) in
guard let `self` = self else { return }
log.debugPrint(KeychainItem.currentUserIdentifier ?? "nil")
switch credentialState {
case .authorized:
// The Apple ID credential is valid. Show Home UI Here
// MARK: Existing iCloud keychain
self.performExistingAccountSetupFlows()
break
case .revoked, .notFound:
let request = appleIDProvider.createRequest()
request.requestedScopes = [
.fullName,
.email
]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
break
default:
break
}
}
}
And this doesn't work which I intended.
Is there any kind teacher who can answer my question?
Thank you.
You don't need to do anything. Apple handles it all for you by associating your app bundle with the identity in the user's iCloud.
If they run your app on another device that has the same iCloud account then they are not given an opportunity to create a new account when they use "Sign in with Apple" - They will simply be prompted to sign in with their existing account.
You can optionally use the code in performExistingAccountSetupFlows() to prompt the user to log in without needing to tap the "Sign in with Apple" button;
When this code runs, if an existing account association is found they will be prompted to authenticate. If no account is found then nothing happens.

When I use the apple to log in, the selection box will pop up. I choose to use the password to continue and the prompt is not complete

iOS13 (beta) Apple Login error
#available(iOS 13.0, *)
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
crprint(error.localizedDescription)
}
Failed to complete operation. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
I've encountered the same issue yesterday and I've managed to fix it following these steps:
Go to https://appleid.apple.com/account/manage, under the Devices section you should find devices on which you are signed in with your Apple ID,
Find device/simulator on which Apple SSO is not working, click on it and click remove from the account,
Go back to your device/simulator settings, it will ask you to authenticate again. When you successfully authenticate, Apple SSO should work again!
I'm not sure what caused this issue, probably some issue between the simulator and Apple ID.
In my case, launching ASAuthorizationController including a request for ASAuthorizationPasswordProvider was causing the error.
Failed to complete operation. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
From the ASAuthorizationError.Code documentation; 1000 is for unknown
ASAuthorizationError.Code.unknown
The authorization attempt failed for an unknown reason.
Declaration
case unknown = 1000
Ref: https://developer.apple.com/documentation/authenticationservices/asauthorizationerror/code/unknown
Now that's not particularly helpful but did give me a clue to check my ASAuthorizationController setup which I was trying to launch with 2 requests from ASAuthorizationAppleIDProvider & ASAuthorizationPasswordProvider, like so:
func loginWithAppleButtonPressed() {
let appleSignInRequest = ASAuthorizationAppleIDProvider().createRequest()
appleSignInRequest.requestedScopes = [.fullName, .email]
let anySignInRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(authorizationRequests: [appleSignInRequest,
anySignInRequest])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
I tried this on a simulator that had an Apple ID with 2FA enabled and also on a device with another Apple ID without 2FA, and both times it would just go to authorizationController(controller:didCompleteWithError error:) and that's it.
Solution:
So to keep it simple, I launched ASAuthorizationController with only ASAuthorizationAppleIDProvider like so:
func loginWithAppleButtonPressed() {
let appleSignInRequest = ASAuthorizationAppleIDProvider().createRequest()
appleSignInRequest.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [appleSignInRequest])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
And voilà! This time things worked as expected:
When using an Apple ID with 2FA
popped up with the login request
When using an Apple ID without 2FA
popped up an error telling me to enable 2FA
called authorizationController(controller:didCompleteWithError error:) with error 1000
So seems that in my case ASAuthorizationPasswordProvider was the culprit but since ASAuthorizationError.Code.unknown is a generic error case, this solution may not work for you.
Also, In my case I need only ASAuthorizationAppleIDProvider for Apple ID sign in so dropped the support for ASAuthorizationPasswordProvider.
In my case i needed to first check ASAuthorizationPasswordProvider, then, if there are no stored credential, use ASAuthorizationAppleIDProvider. For this case i had to make some crunches. Code below:
// Initial point
public func fire(appleIDCompletion: #escaping AppleIDServiceCompletion) {
self.completion = appleIDCompletion
let requestPassword = ASAuthorizationPasswordProvider().createRequest()
performRequest(requestPassword)
}
// help function
private func performRequest(_ request: ASAuthorizationRequest) {
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
// delegate
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
if let e = error as? ASAuthorizationError {
switch e.code {
case .canceled:
trace("User did cancel authorization.")
return
case .failed:
trace("Authorization failed.")
case .invalidResponse:
trace("Authorization returned invalid response.")
case .notHandled:
trace("Authorization not handled.")
case .unknown:
if controller.authorizationRequests.contains(where: { $0 is ASAuthorizationPasswordRequest }) {
trace("Unknown error with password auth, trying to request for appleID auth..")
let requestAppleID = ASAuthorizationAppleIDProvider().createRequest()
requestAppleID.requestedScopes = [.email, .fullName]
requestAppleID.requestedOperation = .operationImplicit
performRequest(requestAppleID)
return
} else {
trace("Unknown error for appleID auth.")
}
default:
trace("Unsupported error code.")
}
}
completion?(.rejected(error))
}
Works like a charm 🔥
Simply Add + "Sign In with Apple" from Capability.
I've resolved it by adding sign in with apple as key in entitlements plist .
From Apple's example,
performExistingAccountSetupFlows, only call this method once on viewDidAppear. If user info exists already then Apple will show it to login. If not then it will throw error.
handleAuthorizationAppleIDButtonPress, whenever user taps on Sign in with Apple button, note that if an account already had existed it would have shown it to the user already. I believe its still in progress and not all use cases are covered, for example if user sees the login info initially from ViewDidAppear call and cancels it then user have to create a new account when tapping on this method since its missing ASAuthorizationPasswordProvider request. If user had some login info then in that case this call (with ASAuthorizationPasswordProvider) will succeed but if no data is available then user will not see any action on tapping this button since it will throw error.
I am still figuring this out, if I have anything more to add then I will update the answer. So, for now we can only have this one use case to use this Sign in with Apple option.
Update:
Once I created a new account, I was offered by this same flow to login with the already existing account. So, I can say that there is no need to include call to ASAuthorizationPasswordProvider request in handleAuthorizationAppleIDButtonPress method. I am doing all the testing on device.
You can always go to Settings -> AppleId -> Password & Security -> Apple ID Logins to check and delete account if you need to test various scenarios.
Update 2:
Everything seems to work fine in other scenarios too if you already have a saved password or App Id account created, so even if I pass ASAuthorizationPasswordProvider in the handleAuthorizationAppleIDButtonPress call, it is working fine. I would suggest to not pass ASAuthorizationPasswordProvider in the next call and keep the flow as described above, this way if no saved password is present or Apple Id created then it will provide option to the user to create a new id, if there is already an id that exists then it will show that id.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
performExistingAccountSetupFlows()
}
func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]
// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
#objc
func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
I resolved this by holding my finger down on finger print scanner til completion. I'm not an iphone user so I'm not used to the finger print scanner. If you pull your finger off too soon you get this error.

Spotify SessionManager continually failing with error "invalid_grant"

What I'm trying to do:
I am implementing the Spotify SDK into my iOS project. I am successfully receiving access tokens for Spotify's API as I am able to do things like search artists, search songs, and view playlists using said API.
The one thing I am struggling to do is play music with the SDK. I have a button that, upon clicking, I want the following flow to happen:
I request Spotify access by doing the following function and using the following Session Manager:
let SpotifyClientID = "###"
let SpotifyRedirectURL = URL(string: "bandmate://")!
lazy var configuration = SPTConfiguration(
clientID: SpotifyClientID,
redirectURL: SpotifyRedirectURL
)
lazy var sessionManager: SPTSessionManager = {
if let tokenSwapURL = URL(string: "https://bandmateallcaps.herokuapp.com/api/token"),
let tokenRefreshURL = URL(string: "https://bandmateallcaps.herokuapp.com/api/refresh_token") {
configuration.tokenSwapURL = tokenSwapURL
configuration.tokenRefreshURL = tokenRefreshURL
configuration.playURI = ""
}
let manager = SPTSessionManager(configuration: configuration, delegate: self)
return manager
}()
func requestSpotifyAccess() {
let requestedScopes: SPTScope = [.appRemoteControl, .userReadPrivate]
self.sessionManager.initiateSession(with: requestedScopes, options: .default)
}
Upon initiation of a SPTSession, I want to connect my remote:
lazy var appRemote: SPTAppRemote = {
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.delegate = self
return appRemote
}()
func sessionManager(manager: SPTSessionManager, didInitiate session: SPTSession) {
self.appRemote.connectionParameters.accessToken = session.accessToken
self.appRemote.connect()
}
Upon app connection, I want to play the ID of a Spotify track that is declared globally:
var pendingSpotifyId: String!
func appRemoteDidEstablishConnection(_ appRemote: SPTAppRemote) {
print("connected")
self.appRemote.playerAPI!.delegate = self
self.appRemote.playerAPI!.subscribe(toPlayerState: { (result, error) in
if let error = error {
debugPrint(error.localizedDescription)
} else if self.pendingSpotifyId != nil {
self.appRemote.playerAPI!.play(self.pendingSpotifyId, callback: { (any, err) in
self.pendingSpotifyId = nil
})
}
})
}
My problem:
This flow is broken up as any time I try to initiate a session, sessionManager(manager: SPTSessionManager, didFailWith error: Error) is always called returning the following error:
Error Domain=com.spotify.sdk.login Code=1 "invalid_grant" UserInfo={NSLocalizedDescription=invalid_grant}
I need the session to initiate successfully so that sessionManager(manager: SPTSessionManager, didInitiate session: SPTSession) can be called and I can connect my remote and, ultimately, play my Spotify track.
What I've tried:
I have ensured a number of things:
Ensured the state of the Spotify app in the background on the user's device is playing (per this ticket: https://github.com/spotify/ios-sdk/issues/31)
Ensured that the correct scopes are in place when receiving an access token. Returned JSON looks something like:
{"access_token":"###","token_type":"Bearer","expires_in":3600,"refresh_token":"###","scope":"app-remote-control user-read-private"}
Things I'm suspicious of:
I am unaware if my token swap via Heroku is being done correctly. This is the only reason I can think of as to why I would be getting this issue. If I am able to use the Spotify API, is this evidence enough that my token swap is being done correctly? (I suspect it is)
Here's what we found out, hope it will help:
The SpotifySDK tutorial doesn't mention that Bundle ID and App Callback URL must precisely match across App Info.plist, source code, Spotify App Dashboard and Heroku Env Vars. The Bundle ID used must match your application Bundle ID.
The App Callback URL must not have empty path, ie: my-callback-scheme://spotify-login-callback
When using both Web Spotify SDK and iOS Framework Spotify SDK in app, take care that only one of them performs auth. Otherwise the App Callback URL will be called twice resulting in error.
The Spotify configuration.playURI may need to be set to empty string rather than nil. Sample app has a note on it.
It's best to have only one instance of object managing Spotify auth in the app. Otherwise ensuring that the correct object is called from the AppDelegate open url method can be tricky.

Login impossible with Spotify iOS 9

I can't login with ios sdk spotify.
I followed the Brian's tutorial (https://www.youtube.com/watch?v=GeO00YdJ3cE) and there is a difference with the current spotify tutorial :
In the video it talks about token exchange and show a spotify webpage. However on current spotify webpage the paragraph is missing.
Does this exchange token must be installed ?
I defined all elements in my spotify app account.
I also defined in the URL schemes : "spotify-action", "my-app-Name" but I can't sucess login.
Anyone help please ?
There are two options:
1) Implicit Grant Flow - grants users access tokens that will expire in 60 minutes - it is much simpler but has it's limits if you want to build a usable application. I will give you a simple example with spotify's updated sdk framework (you don't have to use safari)..
class ViewController: UIViewController, SPTAuthViewDelegate {
let kclientID = ""
let kcallbackURL = ""
#IBAction func loginSpotify(sender: AnyObject){
SPTAuth.defaultInstance().clientID = kclientID
SPTAuth.defaultInstance().redirectURL = NSURL(string: kcallbackURL)
SPTAuth.defaultInstance().requestedScopes = [SPTAuthStreamingScope]
SPTAuth.defaultInstance().sessionUserDefaultsKey = "SpotifySession"
SPTAuth.defaultInstance().tokenSwapURL = NSURL(string: ktokenSwapURL) //you will not need this initially, unless you want to refresh tokens
SPTAuth.defaultInstance().tokenRefreshURL = NSURL(string: ktokenRefreshServiceURL)//you will not need this unless you want to refresh tokens
spotifyAuthViewController = SPTAuthViewController.authenticationViewController()
spotifyAuthViewController.delegate = self
spotifyAuthViewController.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
spotifyAuthViewController.definesPresentationContext = true
presentViewController(spotifyAuthViewController, animated: false, completion: nil)
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didLoginWithSession session: SPTSession!) {
print("Logged In")
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didFailToLogin error: NSError!) {
print("Failed to Log In")
print(error)
authenticationViewController.clearCookies(nil)
}
func authenticationViewControllerDidCancelLogin(authenticationViewController: SPTAuthViewController!) {
print("User Canceled Log In")
authenticationViewController.clearCookies(nil)
}
}
2) Authorization Code Flow - Spotify's authentication server sends an encrypted refresh token which you store, for example,
SPTAuth.defaultInstance().sessionUserDefaultsKey = "SpotifySession". When that sessions expires you must trigger a function on your server... Hope this helps you get started

Resources