On my mobile app I use apple-id authorization. My implementation uses ASAuthorizationAppleIdProvider class and does not require additional proxy web-application, that sends request to Apple. All interaction is done on the mobile client.
All things work ok, and I get authorized and I get IdentityToken from Apple.
Now, I want to send this IdentityToken (looks like "AjzN91mNajN3401jazOs001m3ks") to the server. And on the server side I want to extract user's email from this token.
For Google to solve the same task I have to send GET request with token, like that
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=google_token
and if token is valid in response I get JSON with user's email inside.
How can I do the same for the Apple using Apple's identity key?
Update #1:
My project has 2 parts, client part (frontend) and server part (backend).
The functionality to obtain IdentityToken looks like that (AuthManager is just a delegate):
var provider = new ASAuthorizationAppleIdProvider();
var req = provider.CreateRequest();
authManager = new AuthManager(UIApplication.SharedApplication.KeyWindow);
req.RequestedScopes = new[] {
ASAuthorizationScope.FullName,
ASAuthorizationScope.Email
};
var controller = new ASAuthorizationController(new[] {
req
}) {
Delegate = authManager,
PresentationContextProvider = authManager
};
controller.PerformRequests();
ASAuthorizationAppleIdCredential credentials = await authManager.Credentials;
When I get credentials, there is credentials.IdentityToken property available.
Now, I want to send this identity token to the server, to let the server check this token and obtain user's email using this token from the Apple server, like I do for Google (described above).
And I do not understand, how can I do that.
What Apple endpoint and what HTTP request (GET, POST) should be used to achieve this task?
In OpenID Connect, the Identity Token is never sent to the Provider. I think this is just a typo/naming issue and you mean the Access Token.
The end result of the user authenticating is two tokens:
the Access Token, an opaque token which is not meant to be introspected by the Client. It may or may not be a JWT.
an ID Token, a JWT which contains the user claims.
To obtain the user's email address, decode the ID/Identity Token's JWT payload. To do this in Swift, see these SO answers. The JWT should contain an email value. It looks like the email address may also be an instance property of ASAuthorizationAppleIdProvider, so you should be able to get them from credentials.email.
There does not appear to be a way to directly validate the Access Token. Most OpenID Connect Providers offer a Userinfo Endpoint, or a Token Instrospection Endpoint (I think this is the Google endpoint that was linked in question), but Apple does not. A number of steps were already performed to obtain the Access Token, however, which should make it impossible to forge. If you really only want the email address, though, JWTs are cryptographically signed, so verifying the JWT should guarantee it was issued by Apple. You can also verify the Refresh Token as shown in Apple Developer docs. In your code above, I don't see a way to access Refresh Token, but if you followed an alternative flow as shown in one of the tutorials here or here, you would be able to.
Related
I'm implementing Google's 'code model' of Oauth2 and having trouble getting users' email - I wonder if this is a scopes problem or my misunderstanding about how to set up the code model. This sequence of events is already working:
Client loads https://accounts.google.com/gsi/client
Client starts call to google.accounts.oauth2.initCodeClient
Client gets code
Client passes code to one of my server endpoints
Server has an oauth2Client set up using the config with client_id, client_secret, and redirect URL = 'postmessage'
Server exchanges the code from the client for tokens
Server does oauth2Client.setCredentials(tokens) - this contains an access_token, which is enough for the client to make API calls to, e.g., retrieve the user's Google Calendar
Server is able to do oauth2Client.getTokenInfo(tokens.access_token);
There are various places along the way that involve scopes; I am probably getting something confused here. The client's initial call (step 2 above) uses
scope: 'https://www.googleapis.com/auth/calendar',
My code path on the server does define scopes anywhere.
In GCP, my project is set up with scopes
calendar.calendarlist.readonly, calendar.readonly and calendar.events.readonly
openid
/auth/userinfo.email
Here's the problem I'm encountering: when I go through this flow as a user and oauth with the account that owns the GCP project (this is a Google Workspace email, in case that matters), the tokens object that the server receives (step 6 above) has access_token, refresh_token and id_token - the id_token can be decoded to yield the user's email, and the user's email is also in the response to oauth2Client.getTokenInfo(token.access_token).
However, when I go through the flow with my other (personal) Gmail account, the tokens object that the server receives is missing the id_token but has the access and refresh tokens. Question 1: why are the responses different?
Question 2: How can I get the email of the user on the server in the personal Gmail account case? I've tried having the server make a call to https://www.googleapis.com/oauth2/v2/userinfo?fields=id,email,name,picture with the access_token, but this fails. I am not sure if I'm supposed to declare scopes for oauth2Client somehow, or tap a Google API using a different method on the server.
I think I've had a breakthrough: in step 2 in my original post, when I did "Client starts call to google.accounts.oauth2.initCodeClient", I had set the scope of initCodeClient to just the calendar scope. When I changed it instead to scope: 'https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/userinfo.email openid', (scope takes a space-delimited list in this case), it allowed my server call to get the id_token for this user and oauth2Client.getTokenInfo to get a response with the user's email in it.
When I updated the scopes like that, the popup asking for authorization also updated to request all the scopes I wanted - previously, it was only asking for the Calendar scope, so it makes sense Google didn't want to return the email.
What I still don't understand is why my previous setup was working for the account that owns the GCP project. In other words, when I was first building it out with that owner account, the client was only noting the Calendar scope while the server was asking for all three scopes (ie there was a mismatch), and the server was still able to get an id_token and the user's email in getTokenInfo. Maybe the owner account has some special privilege?
I’m updating a third party app that currently integrates with Twinfield using the session’s method with username and password to use the oAuth method.
In confused by the documentation though... do I still need to use the sessions or when using oAuth do I just call the endpoint(s) by passing the access token in the header as normal?
Also their Soap definition has four properties, the usual ClientID and Secret but also accessSecret? What’s that?
TLDR: you no longer need to use the sessions and SelectCompany; when you have the access token you can use that and the company code directly in the header.
You can obtain the access token as described here.
The documentation is a bit unclear on how to use the access token in your calls.
In the old username/password/session flow, you referred to a SessionID in the SOAP Header, and you would do a SelectCompany call to select the relevant target ("administratie").
In the OAuth flow, the SessionID is no longer relevant. Once you obtained a valid access token, you should set that in the header using the AccessToken field.
Instead of the old SelectCompany call, you can set the CompanyCode directly in the header. So if you have obtained an access token eyWhatANiceToken, and want to retrieve data for company "My Company BV [130001]" you have set AccessToken to eyWhatANiceToken and CompanyCode to 130001 in the header.
You can request the available codes using the list offices call
I'm working with the Snapchat API to try and log into an app using their OAuth flow. Once the user is logged in via Snapchat, I'm trying to reverse engineer (since they have no documentation on this) how to obtain some sort of unique ID for the user so I can associate them with a local user in my database. This is how I have previously done this with things like Facebook. The user logs in and gets an access token via the Facebook Api, and I can extract some kind of unique ID for the user via the Facebook API.
The Snapchat API only allows you to access the user's display name and some "externalId", which I cannot guarantee won't change. So I decoded the JWT token that Snapchat issued to me and it challenged my understanding of how JWT tokens work. When I decoded the token at http://jwt.io, I saw that the payload was empty, yet the token works when calling the https://kit.snapchat.com/v1/me endpoint. How is the snapchat server able to identify who I am? I've always had the understanding that the JWT must include a claim such as sub which identifies the user. The server can then use that information to know who I am.
In this case, my JWT payload is empty, but the aforementioned endpoint still returns my user data. What is happening here? How does the server know who I am when my JWT token has an empty payload? To me they must be storing a copy of my JWT token on the server, which seems like the incorrect way to use JWT tokens. Perhaps my understanding is terribly wrong. Any thoughts?
The payload of a JWS (signed token) can be detached and transmited to the audience by other means.
This feature is described in the Appendix F of the specification.
With the JWS compact serialization mode (the most common format), a token looks like THE_HEADER.THE_PAYLOAD.THE_SIGNATURE. With a detached payload it is identical except that THE_PAYLOAD is an empty string: THE_HEADER..THE_SIGNATURE.
The verification of the signature is the same as with an attached payload. The receiver should have received the payload and must recreate the full input i.e. THE_HEADER.THE_PAYLOAD.
Regarding the identification performed by snapshat, a reference to the detached payload may be set in a header parameter of the token (first part of the token) allowing Snapchat to fully verify the token.
I have a client API, that is a confidential client. When I authenticate with an open id provider, I am redirected to my callback with an authorization code, which is immediately exchanged to receive a refresh token, an access token, and an ID token.
Now, I create a session cookie that has a uuid for the authenticated user. When the user makes a request, do I...
Use my access token to call the providers userinfo endpoint to get the user info.
Read the validated ID token to get the users info.
When it comes to using the refresh token I see 2 options:
After reading a valid ID token or access token during a request, use the refresh token to get a new access or ID token to store at a new uuid, which is returned to the user with an updated cookie. While requiring the user to sign in more, this means the users session becomes invalid after inactivity on their part equaling the lifetime of the access or ID token. This is potentially more secure.
Use the ID token or access token until valid and then refresh to get a new one. If the refresh never expires, the user will never have to sign in again even if inactive for a long period of time ( unless cookie expiration is low ) Potentially less secure.
Thoughts?
A few notes first:
the lifetime of the application session is (typically) independent of the lifetime of the ID token; the latter is just an assertion about the user's identity, it doesn't represent a session
your first option doesn't work with a parallel requests e.g. when a user has opened multiple tabs to your application or the application uses Javascript calls
But foremost: a refresh token should not be used to get a new ID token, it should only refresh the access token; a user needs to be present to get a new ID token with the same semantics as the original one.
In short, you only use an authentication token to access userinfo_endpoint uri.
OpenID Connect allows the use of a "Discovery document," a JSON document found at a well-known location containing key-value pairs which provide details about the OpenID Connect provider's configuration, including the URIs of the authorization, token, revocation, userinfo, and public-keys endpoints.
You can research each applications unique discovery page uri from their docs for example here is
Google
You make a get request to the discovery document uri and from this document you find the userinfo_endpoint uri.
Example response from microsoft
GET https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration
{
"authorization_endpoint": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize",
"token_endpoint": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"private_key_jwt"
],
"jwks_uri": "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys",
"userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo",
"subject_types_supported": [
"pairwise"
],
...
}
Google's discovery doc uri
GET https://accounts.google.com/.well-known/openid-configuration
Get an Authorization token. For example pull up Network -> Fetch/ XHR now look around and try to find a request header with the key 'authorization'. Copy 'Bearer {the id}' and put in the header of a get request like the picture shown below.
GET or POST /oidc/userinfo HTTP/1.1
Host: graph.microsoft.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Il…
Microsoft Example Postman Request
Quick summary... Here's a pseudo-flow to show what I'm trying to accomplish...
Client (angular) -> Server (webapi/WIF)
Log in with username and pwd -> Validate login credentials and return bearer token with claims
Change selected client -> Send new bearer token with updated claims based on selected client
When changing selected client, the existing bearer token will of course be sent, so the server can use that to know who I am and verify I can change to the selected client before generating a new bearer token to return
More detail...
I am using the default code generated by WIF to log users in and return a bearer token that is then sent with every request from my app (angular). I call the TOKEN endpoint with my username and password, GrantResourceOwnerCredentials is called, the user is authenticated, the claims are set, and the access_token is returned. All well and good.
Here's the scenario I don't know how to handle. When the user is logged in, they have the claims for the default client to which they are assigned. However, they can change the client they're accessing at any time while logged in, but each client comes with its own set of claims, meaning that the bearer token needs to be replaced with one for the selected client.
I keep thinking there's a "public string ReturnNewBearerToken(AuthenticationTicket ticket)" function somewhere that would let me get a new token and return it as a string. But I've scoured the web and can't find anything to do this. Everything I've seen suggests that you have to log out and log back in to update your token, which I can't believe is the case. I can't know what client the user wants to access the app as until the log in, but once they're logged in I can't update the token? That would be a catch 22.
So, does anyone know how to do this with WIF?
The answer is in my comments above. The jist is that the line...
var tokenStr = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
is what I needed to be able to return an encoded ticket as a bearer token to my angular app.