Sign in with Apple - ios

I'm trying to implement Sign in with Apple since it's required now when you have other third party login library. My question is, is there a way for me to get apple's auth token just like from facebook and google login? All I could get is identity token and authorization code from ASAuthorizationAppleIDCredential. I am not quite sure what is used to get user information using our API.
Thank you so much for the answer.

in ASAuthorizationControllerDelegate
get the token
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
if let data = appleIDCredential.identityToken {
let token = String(decoding: data, as: UTF8.self)
// here send token to server
}
}
}
}
send the token to the server, which must be set up
https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api
and based on this token your server will get the user information from Apple the same way you can get the facebook user data.
Then your api should implement user authentication based on this data (e.g. create user account based on the user email and return your access token to the app)

Related

iOS Google SignIn refreshed idToken has missing profile info in backend authentication

I use GoogleSignIn for iOS (GoogleSignIn-iOS), v6.1.0, in my iOS app.
All calls to my backend have the idToken in the request header.
The id token is verified in the backend. Here I also need to retrieve the users email and name.
(see also: https://developers.google.com/identity/sign-in/ios/backend-auth)
After a new SignIn with GIDSignIn.sharedInstance.signIn everything works fine.
GIDSignIn.sharedInstance.currentUser.profile contains email and name.
When sending the idToken to the backend, the Verifier gives me name and email in its payload, too.
Before I do a backend request, I get a valid (=not expired) idToken, with the following code:
private static func refreshToken(_ authentication: GIDAuthentication) async throws -> GIDAuthentication {
try await withCheckedThrowingContinuation { continuation in
authentication.do { authentication, error in
if let authentication = authentication {
continuation.resume(returning: authentication)
} else if let error = error {
Log.warn("Google SignIn refreshToken failed with -> \(error)")
continuation.resume(throwing: error)
}
}
}
}
I use the following code to get the idToken, before I create the request for my URLSession.
func idToken() async -> String {
do {
guard let user = GIDSignIn.sharedInstance.currentUser else {
Log.error("No GID user to get idToken from")
return ""
}
currentAuth = try await Self.refreshToken(user.authentication) //currentAuth is a class variable
return currentAuth?.idToken ?? ""
} catch {
print("Error during Google SignIn idToken retrieval \(error)")
return ""
}
}
And now my problem comes:
The idToken is refreshed properly. It is valid for another hour, and the verifier in my backend accepts it.
But I can't get the users name from the verified payload data in the backend, the name field is null.
Same happens when I use GIDSignIn.sharedInstance.restorePreviousSignIn (which I call on every app re-start, to do the silent sign in. (But in the app, the values are there in the updated users object profile)
It seems to me, that when the idToken gets refreshed, that it looses the profile scope.
I hope someone can help me with this, or at least explain the behaviour to me.
Thank in advance :)
Update
I checked the idTokens on https://jwt.io.
They are valid, but after the refresh, the jwt payload definitely is missing the profile data, like the users name.
I waited one day and tried again. Now the silent signin after app start gives me a complete idToken with jwt payload including name, but only once. After an hour, when the idToken gets refreshed, the idToken is again missing the profile information
Unfortunately I got no hint here, so I solved my problem as follows.
I hope this approach can save time for some others in the future.
I only require the profile data, when the user logs in to the backend the first time and a new user record is created in the backend.
In all other calls, where I need the JWT for authentication, I only rely on the basic information (ID, email) and handle all other values as optional values.
So I check the users name, if it is available. Otherwise the ID and a valid token is of course sufficent for authentication.

AWS Amplify iOS SDK : FederatedSignIn Failed to retrieve authorization token on Amplify.API.post

I've been working with the Amplify SDK to get federatedSignIn working with my iOS app with "Sign in with Apple" and Cognito to eventually make calls to API Gateway / Lambda functions.
TL;DR : My access token does not appear to be "automatically included in outbound requests" to my API as per the last paragraph of this section of the docs : Cognito User pool authorization
I have successfully authenticated using the tutorial found here Authentication Getting Started and other various Youtube videos on the Amazon Web Services channel.
Upon successful sign in through Apple I'm given an ASAuthorizationAppleIDCredential object. This contains the user field (token) which I pass to the Amplify.Auth class using the following Swift code :
func signIn (with userId: String)
{
guard
let plugin = try? Amplify.Auth.getPlugin(for: AWSCognitoAuthPlugin().key),
let authPlugin = plugin as? AWSCognitoAuthPlugin,
case .awsMobileClient (let client) = authPlugin.getEscapeHatch()
else
{
return
}
client.federatedSignIn(providerName: AuthProvider.signInWithApple.rawValue, token: userId) { (state, error) in
if let unwrappedError = error
{
print (unwrappedError)
}
else if let unwrappedState = state
{
print ("Successful federated sign in:", unwrappedState)
}
}
}
All appears to be successful and to double check I use the following bit of code to ensure I'm authorized :
func getCredentialsState (for userId:String)
{
let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: userId) { (credentialsState, error) in
if let unwrappedError = error
{
print (unwrappedError)
}
switch credentialsState
{
case .authorized:
print ("User Authorized")
case .notFound, .revoked:
print ("User Unauthenticated")
case .transferred:
print ("User Needs Transfer")
#unknown default:
print ("User Handle new use cases")
}
}
}
In the console I see "User Authorized" so everything appears to be working well.
However when I then go to make a call to Amplify.API.post I get the following error:
[Amplify] AWSMobileClient Event listener - signedOutFederatedTokensInvalid
Failed APIError: Failed to retrieve authorization token.
Caused by:
AuthError: Session expired could not fetch cognito tokens
Recovery suggestion: Invoke Auth.signIn to re-authenticate the user
My function for doing the POST is as follows :
func postTest ()
{
let message = #"{'message": "my Test"}"#
let request = RESTRequest (path: "/test", body: message.data(using: .utf8))
Amplify.API.post (request:request)
{
result in switch result
{
case .success(let data):
let str = String (decoding: data, as: UTF8.self)
print ("Success \(str)")
case .failure(let apiError):
print ("Failed", apiError)
}
}
}`
I then went into the API Gateway UI and changed the generated Method Request on my resource from AWS IAM to my Cognito User Pool Authorizer thinking this was the issue. I also changed the awsAPIPlugin authorizationType to "AMAZON_COGNITO_USER_POOLS" in my amplifyconfiguration.json file. This unfortunately did not have any affect.
I've seen posts such as this issue User is not created in Cognito User pool for users logging in with Google federated login #1937 where people discuss the problem of having to to use a web ui to bring up the social sign in. I understand that Apple will reject your app sometimes for this. Therefore this is not a solution.
I then found this post which seems to resolve the issue however this appears to use the old version of the SDK? Get JWT Token using federatedSignIn #1276
I'm not great with Swift (I'm still an Objective C expert, but am slowly learning Swift) so I'm uncertain which path to go here and whether this is actually a solution? It does seem to be quite more complicated than the function I have that does my POST? The RESTRequest does seem to be a simple and easy solution but I'm uncertain how to pass it the Authorization token (or even how to get the token if it is needed here).
However, everything I've read about the SDK is that the authorization should be handled automatically in the background according the docs in my first link above. Specifically pointed out, again, here : Cognito User pool authorization. The last paragraph here states đź‘Ť
With this configuration, your access token will automatically be included in outbound requests to your API, as an Authorization header.
Therefore, what am I missing here as this does not appear to automatically include my access token to my outbound requests to my API?

How to authenticate with Apple SSO (TV Provider) using VideoSubscriberAccount framework

I need to implement authentication scheme using Apple SSO for my application:
Check for an signed user in Settings TV Provider
Sing in into TV Provider from my app if there is no a signed user
Get authentication payload of a signed user from my provider backend with tokens, uuid, etc.
What the main steps to config and implement Apple SSO authentication using VideoSubscriberAccount framework because unfortunately there is no much info and samples about?
There are several common steps to implement authentication scheme with Apple SSO:
1. Config your provisional profile, .entitlements and Info.plist files
YourApp.entitlements file must have a special key that enables SSO for your app:
com.apple.developer.video-subscriber-single-sign-on Boolean YES
This entitlement also should be present in your provisional profile e.g.:
Info.plist must have next key with a message that will be shown to user on first access to the video subscriptions:
NSVideoSubscriberAccountUsageDescription String "This app needs access to your TV Provider."
2. Create an account manager instance and implement delegate methods to coordinate access to a subscriber's account.
import VideoSubscriberAccount
...
let accountManager = VSAccountManager()
accountManager.delegate = self
...
extension YourController : VSAccountManagerDelegate {
func accountManager(_ accountManager: VSAccountManager, present viewController: UIViewController) {
window?.rootViewController?.present(viewController, animated: true, completion: nil)
}
func accountManager(_ accountManager: VSAccountManager, dismiss viewController: UIViewController) {
viewController.dismiss(animated: true, completion: nil)
}
func accountManager(_ accountManager: VSAccountManager, shouldAuthenticateAccountProviderWithIdentifier accountProviderIdentifier: String) -> Bool {
return true
}
}
3. Determine the state of the application's access to the user's subscription information.
accountManager.checkAccessStatus(options: [VSCheckAccessOption.prompt : true]) { status, error in
...
}
If the app tries to access to subscription information first time next prompt will be shown:
4. Request information about the subscriber's account.
If access is granted you can make a metadata request to check for a signed user:
if case .granted = status {
let request = VSAccountMetadataRequest()
request.includeAccountProviderIdentifier = true
request.isInterruptionAllowed = true
accountManager.enqueue(request) { metadata, error in
...
}
}
The provider selection list and sign-in form will be shown if there is no signed account:
To skip providers list view you can set supported providers identifiers to the request e.g.:
request.supportedAccountProviderIdentifiers = ["Hulu"]
5. Second metadata request with required attributes from a identity provider's info endpoint
If there is no error and signed account is present you should call to your identity provider's info endpoint to obtain required attributes for second metadata call such as:
attributeNames: a list of SAML attributes needed
verificationToken: Base64 encoded signed authentication request from the service provider to the identity provider
channelIdentifier: service provider entity id
And make second request with these parameters:
request.attributeNames = attributeNames
request.verificationToken = verificationToken
request.channelIdentifier = channelIdentifier
accountManager.enqueue(request) { metadata, error in
...
}
6. Request translation of SAML authentication response
The seconds metadata request responds with Apple's SAML payload that should be sent to your identity provider's translation endpoint then the endpoint parses that element and returns a response that resembles the authentication payload e.g.:
if let samlPayload = metadata?.samlAttributeQueryResponse {
let body = [
...
"saml" : samlPayload
]
fetch("https://your.identity.provider/saml/translate", httpBody: body)
}
The endpoint should respond with JSON which contains all your authentication data: tokens, uuid etc.

ASAuthorizationAppleIDCredential : How to check in backend server that the credential is valid? [duplicate]

This question already exists:
Apple Sign In : Validating identityToken at server side
Closed 2 years ago.
I m implementing User Authentication with Sign in with Apple. when the authentication succeeds, the authorization controller invokes the authorizationController:didCompleteWithAuthorization: delegate function, which the app uses to store the user’s data in the keychain.
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
.. send the authorizationCode / identityToken / user and email to the server.
server must verify that everything is valid and is yes login the user ...
}
}
How in my back end server can I verify the authenticity of the authorization ? Is their any apple endpoint available somewhere (like google does with https://oauth2.googleapis.com/tokeninfo?id_token=) to validate for me the authorization ?
You need to contact your backend for what type of login is supported by backend. If they are using appleIDCredential.authorizationCode or appleIDCredential.identityToken, use them.
For my case i was using firebase, then this can be done using OAuthProvider which can be created using :
OAuthProvider.credential(withProviderID: <#T##String#>, idToken: <#T##String#>, rawNonce: <#T##String?#>, accessToken: <#T##String?#>)
Source: Apple docs

Firebase: How does it know what account my OAuth token is associated with?

I'm about to build an iPad app that uses OAuth 2.0 using this cocoapod: https://github.com/OAuthSwift/OAuthSwift. I'm looking through this firebase tutorial (https://firebase.google.com/docs/auth/ios/custom-auth) but, I'm a little confused.
Given I have this code:
oauthswift = OAuth2Swift(
consumerKey: "********",
consumerSecret: "********",
authorizeUrl: "https://api.instagram.com/oauth/authorize",
responseType: "token"
)
let handle = oauthswift.authorize(
withCallbackURL: URL(string: "oauth-swift://oauth-callback/instagram")!,
scope: "likes+comments", state:"INSTAGRAM",
success: { credential, response, parameters in
print(credential.oauth_token)
FIRAuth.auth()?.signIn(withCustomToken: credential.oauth_token ?? "") { (user, error) in
// ...
}
},
failure: { error in
print(error.localizedDescription)
}
)
How would firebase know what account that token is associated with if the token is random and different for each device? I don't know how to associate that token to an account. Like if I signed in to instagram with username cool#example.com and password blah123 and the oauth_token was 123abc how would I know that 123abc was connected to cool#example.com in my firebase account if 123abc was random?
I feel like I'm misunderstanding one of the concepts.
You need to create the custom token from the Instagram access token server-side using the Firebase SDK's. You then need to request this from the server via the client and signIn withCustomToken: using the token in the response.

Resources