Access tokens and id tokens - oauth-2.0

I am pretty new with these protocols, and I am having some trouble understanding something.
I am currently working on an application which API and Frontend is mine, I use azure identity platform to receive the tokens on the clientside and send the token to the server that validates the token using passport-azure-ad bearerStrategy. (I have my app registration for that purposes ofcourse).
The thing that I don't get, is that I missed correctly used the tokens I received from azure in my client and sent the ID Token to my API, it verifes it as a valid one and user is authenticated to perform the request sent.
But, as I read here https://learn.microsoft.com/en-us/azure/active-directory/develop/id-tokens, and in any other article about oAuth2 and openID, ID tokens are for UX stuff and client, while I should have used the access token in my request to my API.
But howcome the ID Token is also verified in my API? It makes no sense for me, or am I missing something?
And if so, is there any vurlnabilty in using Id Token as I did?
Thank you!

APIs should first validate the JWT access token, to check these fields have allowed values. An ID token will then fail the audience check.
JWT signature
Not expired / valid at this time
Issuer (a Microsoft ID)
Audience (eg api.mycompany.com)
Access tokens have scopes, whereas ID tokens do not. Every API endpoint should validate the received scope, eg to ensure that it has received the right type of token. This will also ensure that the API does not accept ID tokens.
So although some API tech stacks accept ID tokens, making the standard checks will ensure the right behavior. And the real API authorization is then done using claims, to apply your business rules.

Related

How do you guarantee safety when validating Keycloak (OIDC) tokens without making requests to the Keycloak API?

There is a lot of information on OAuth, OIDC and Keycloak, but the main thing every tutorial seems to gloss over is offline validation. The only information I found is in the Keycloak docs on RPT introspection:
No. Just like a regular access token issued by a Keycloak server, RPTs also use the JSON web token (JWT) specification as the default format. If you want to validate these tokens without a call to the remote introspection endpoint, you can decode the RPT and query for its validity locally. Once you decode the token, you can also use the permissions within the token to enforce authorization decisions.
If I wanted to verify a user's request with an authorization token, I would make a request to the Keycloak introspection (or userinfo?) API. I'm not completely sure, but I would guess that Keycloak then verifies the info encoded in the JWT with the Keycloak user database.
However, what if I don't want to make a Keycloak request on every API request? This could improve system performance by limiting the amount of HTTP requests. There are mentions of JWT signature validations and reading the scope and user information encoded in the JWT, but I don't see how this guarantees safety. Isn't it possible to just generate any old JWT, encode any information you want and basically spoof an authorization token? How would I check if the user mentioned in the JWT really exists in the Keycloak database?
I think I am missing some crucial part of this technology.
For example, in ASP.NET Core, an API that receives a JWT token will, at startup, download the public signing keys from the token provider, and by default, it will refresh the keys every 24 hours.
So, when the API receives a JWT token, it will do various checks to validate the token, including:
Validating the signature using the provider public signing key
Validate the audience claim (is the token intended for me?)
Validate the expiry date
The API should not need to query anything against the token provider (keycloak) to let the user in.
However, then we have authorization (What the user is allowed to do), which is a different thing, and that all depends on your requirements.
JWT is all about who the user is; if the token is valid, you can trust that information.
With JWT-tokens, your API can work almost offline from the token provider. It can even be 100% disconnected if you copy the public signing key manually into the API.

Extending OAuth2 MS AD access_token data

I am missing some understanding of OAuth2 access_token hope someone can explain or guide me to what I am missing.
I am using Microsoft Azure AD as an authentication provider for my application, I used the returned id_token after successful authentication to extend it with some additional data custom to my application (to facilitate authorization).
I am doing this throw JWT.sign, I decode the data from id_token and add data then I sign it using a secret key saved at the server.
My question is, can I do the same for access_token? When I tried to do so, I get unauthorized.
Am I doing something wrong? Or this is not possible? And why is this happening, I don't find any request made to MS to validated my new signed access_token.
You should never change tokens issued - this is not a correct thing to do. But your point about using domain specific claims is totally valid - all real world systems need these for their authorization.
OPTION 1
Some specialist providers can reach out at time of token issuance and contact your APIs, to get domain specific data to include in tokens. See this Curity article for how that works. I don't think Azure AD supports this though.
PRIVACY
It is best to avoid revealing sensitive data in readable tokens returned to internet clients. If you include name, email etc in ID tokens or access tokens this may be flagged up in PEN tests, since it is Personally Identifiable Information and revealing it can conflict with regulations such as GDPR.
Curity recommends protecting access tokens by issuing them in an opaque reference token format - via the phantom token pattern.
OPTION 2
An option that would work fir Azure AD is to adopt the following approaches:
Look up extra domain specific claims in your API when an access token is first received, then cache results for further API requests with the same access token. See this Azure AD Code Sample class of mine for some code that builds a custom ClaimsPrincipal. Note that the API continues to validate the JWT on every request.
If the UI needs extra domain specific claims then serve them from your API, which can return both OAuth User Info and domain specific data from its ClaimsPrincipal to the UI. See this API controller class for how that looks. Personally I always do this and never read ID tokens in UIs - which should also never read access tokens.
Applications interacting with Azure AD, receive ID tokens after authenticating the users. The applications use access tokens and refresh tokens while interacting with APIs.
The id_token is a JSON Web Token (JWT) which has user profile
attributes in the form of claims. The ID Token is consumed by the
application and used to get user information like the user's name,
email.
The Access Token on the otherhand is a credential that can be
used by an application to access an API.
So if you need application to access api, there the access token is used and you may follow the suggestion steps provided by Tiny Wang
Similar to id tokens, access tokens are also signed, but they are not
encrypted. As per IETF OAuth (RFC 6749) standard specification ,
access token can have different formats and structures for each
services whereas, id token should be JWT format.
To validate an id_token or an access_token, your app has to validate
both the token's signature and the claims. To validate access tokens,
your app should also validate the issuer, the audience, and the
signing tokens.
So in production application, you should get id token by specifying
“id_token+code” or “id_token+token” as response_type to verify
whether the authentication is correctly succeeded. It means it uses
the id_token for authentication and “code” to exchange access_token
to access the resource for authorization.
In short id_token is used to identify the authenticated user, and the
access token is used to prove access rights to protected resources.
Refer this for the information regarding access token and id token.

Using access_tokens and id_tokens together Auth0

While starting to integrate auth0, I came across this article
So its clear that to secure apis, all we need is the access_token and that is sent with each http request in the request Authorization header(Bearer scheme).
But then auth0(and possibly other providers) also send an Id_token that contains information about the user. My confusion is that how do I use this id_token to pass user information to my api. ( I have a spa running front end that authenticates to auth0 and gets these 2 tokens).
I can ofc call the userInfo end point in my api to get user info. But then wouldn't this defeat the purpose of the Id tokens?
The ID Token is consumed by the application and the claims included,
are typically used for UI display. It was added to the OIDC
specification as an optimization so the application can know the
identity of the user, without having to make an additional network
requests.
So my question is how do I access user profile in my api using id tokens?
"My confusion is that how do I use this id_token to pass user information to my api"
for that confusion, you just pass your JWT token. while generating JWT token, you need to add user information in payload part in JWT token. When your api get the JWT token, just check your JWT token is correct or not by the use of secret key and if correct, you can get data. How to get is just go from that JWT Authentication for Asp.Net Web Api
ID token is sent from the authorization server as a part of OIDC protocol. The purpose of this is to authenticate the user to your client application (SPA in this case). i.e. to let your API or the application know which particular user authorized the client to access a certain resource on its behalf.
Best way to use the ID token is by decoding and verifying it using a library. This will allow you to verify the signature of the token and any other claim that is included in the token (you can add custom claims to the tokens). Validation of those claims can be used to determine identity of the user and match with the user profile in your API. You will have to check the documentation related to your IdP(auth0) to figure out how to add new claims that are used by the user profile in your API.

Using OpenID Connect ID token for application-to-application authentication/authorization

(Apologies in advance for possible misuse of the word authentication/authorization)
I am building up several web applications that talks to each other with an identity provider that looks like this.
A -----> idP
/ \
/ \
/ \
\/_ _\/
B C
Where application A will make requests to B and C to push and pull data, and it has to do so in the context of the user that's using application A. Given that I am using OAuth2 on the idP, my first thought is using the bearer token issued by the identity provider, and pass that around to each application as a way to "login" from application A to B/C.
Problem with that approach is that B/C doesn't know who that bearer token belongs to. In order to make this work, I would have to create an end point on the idP to get user information that B/C can use to figure out the user. While this could work, I wanted to see if there was something more standard out there I can use. That's when I found OpenID Connect.
From what I can see, OpenID Connect basically replaces the bearer token with a JWT, and signs it with a private key to prevent tampering. Given that, here's the flow I am thinking of:
User authenticates against web application A, application A receives ID token from idP, create a logged in session for the user and store the ID token as part of the session
Application A needs to retrieve data from application B in the context of user, so it sends the ID token to application B as part of the request
Application B gets the ID token from the header, gets the public key from the idP, and validates the signature.
If signature is validated, then the rest of the JWT is validated (expiry, issuer, issued time stamp, audience)
If all the checks are valid, then the data is returned from B to A
I have a couple of question about using OpenID Connect in this manner:
In OpenID Connect, the JWT's audience field can contain multiple client IDs, but from what I understand, the idP is suppose to only return the client requesting for the token. So how does the idP know what other client ID to put into the audience field?
How to best handle logout? Since each application maintains its own session, my thinking is that logout should invalidate the individual sessions on application A/B/C, then the session on idP to completely logout. Would that be best handled with an AJAX request to each application?
Although this isn't answering directly your questions, let me add comments to it as I am currently asking myself similar questions. This is also based on my humble understanding.
Regarding the audience (aud claim) of an ID Token, at the time of initial registration, your client registration could include what other applications (registered as OAuth clients too) your client would like to access. This could then be used to always include such an audience list when an ID Token is issued for your client. Check also the azp (authorized party) claim that should contain your client id. That's said, I haven't seen examples of tools managing this at registration time.
About your comment of the ID Token replacing the bearer token (i.e. Access Token), I have been looking at a standard way (e.g. specific Authorization header) to pass an ID Token when calling another application, but I didn't find any. I am not sure if a standard Authorization Bearer should be used or if there is another standard way of passing an ID Token.
Note also that, as far as I saw, ID Tokens have limitations to be used beyond being a short-live token, as:
they cannot be refreshed when they expire (even refreshing the linked Access Token isn't returning a refreshed ID Token)
they cannot be revoked (even if the linked Access Token could be), e.g. to manage a logout
They seem to more intended to be used to initiate a session (in a way similar to SAML assertions), like what you described, but you would rather copy ID Token claims and additional information retrieved from the UserInfo endpoint into your session information as your session may last longer than the duration of the ID Token.
That being said, if we want to use an ID token as some evidence of the user authentication, this wouldn't work after the token is expired, so I am not sure that trying to keep and use it during a whole session is a good idea.
If this is used as part of a short chain of calls or to create a new session with downstream applications (including initiating a JWT grant type OAuth flow with the ID token as an evidence of user identity), this may be what this is targeted at.

What is intent of ID Token expiry time in OpenID Connect?

In OpenID Connect an access token has an expiry time. For authorization code flow, this is typically short (eg 20 minutes) after which you use the refresh token to request a new access token.
The ID token also has an expiry time. My question is what is the intent of this?
Any ID token expiry time less than the expiry time of the refresh token will mean you will eventually have an expired ID token, but a valid access token.
So are you meant to:
give your ID token an expiry longer than the refresh token expiry, or
set it to the same expiry as the access token and take some action (what?) when it expires, or
just consume the ID token in your client on receipt, then ignore the expiry time after that?
The OpenID Connect specification just says that when validating an ID token,
"The current time MUST be before the time represented by the exp Claim."
which (possibly) supports the third option above.
EDIT
As OpenID Connect builds on OAuth2 the answer to the supplementary question below can be found in the OAuth2 specification which says,
expires_in
RECOMMENDED. The lifetime in seconds of the access token.
A related question is when you exchange an authorization code for the tokens, the same specification says you might get a response such as:
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJhbG[...]"
}
But what does "expires_in" relate to in this case? The access token, the refresh token or the ID token?
(For information, IdentityServer3 sets this to the access token expiry time).
I'm answering my own question as have discovered that some of the assumptions behind my question were wrong, so easier to clarify here, rather than re-write the question.
An ID token is meant for proving to a Client that the user has authenticated, and who they are as a result.
When a Client receives an ID token, it will generally do something like convert it to a ClaimsIdentity, and persist this, eg using a cookie.
The ID token has to be un-expired at this point of use (which it should be, since it has just been issued). But after this it is not used again, so it does not matter if it expires while the user still has an active session. The Client has the authentication information it needs, and in turn can choose its own policy for how long the session lasts before the user has to log in again.
My wrong assumption when asking the question was that an ID token and access token should be used together, and therefore both needed to have valid expiry dates. This is wrong for various reasons:
ID tokens are only for authenticating to a Client (as described above).
Access tokens have nothing to do with Clients. They are for access to resources and a Client only handles them if it in turn needs to call an resource.
Something like a standalone MVC or WebForms application only needs an ID token. If it isn't calling an external resource, there is nothing to grant access to, so no access token.
I had to dig into this for my own reasons and wrote it up, so I'll post what I learned here...
First, I'll answer the question at the risk of stating the obvious: The ID token cannot be trusted and its content must be ignored if the current time is greater than the expired time. The questioner's answer states that the after the initial authentication of the user, the ID Token isn't used again. However, since the ID Token is signed by the identity provider, it certainly could be useful at any time to give a way of reliably determining who the user is to other services that an app might be using. Using a simple user ID or email address isn't reliable because it can be easily spoofed (anyone can send an email address or user ID), but since an OIDC ID Token is signed by the Authorization server (which also usually has the benefit of being a third party) it cannot be spoofed and is a much more reliable authentication mechanism.
For example, a mobile app may want to be able to tell a backend service who the user is that is using the app and it may need to do so after the brief period following the initial authentication, at which time the ID Token is expired, and thus, cannot be used to reliably authenticate the user.
Therefore, just like the access token (used for authorization - specifying what permissions the user has) can be refreshed, can you refresh the ID Token (used for authentication - specifying who the user is)? According to the OIDC specification, the answer isn't obvious. In OIDC/OAuth there are three "flows" for getting tokens, The Authorization Code flow, the Implicit flow, and the Hybrid flow (which I'll skip below because it's a variant of the other two).
For the implicit flow in OIDC/OAuth you request the ID Token at the authorization endpoint by redirecting the user in the browser to the Authorization endpoint and including id_token as the value of the response_type request parameter. An Implicit Flow Successful Authentication Response is REQUIRED to include the id_token.
For the Authentication Code flow, the client specifies code as the value of the response_type request parameter when redirecting the user to the authorization endpoint. A successful response includes an authorization code. The client client makes a request to the token endpoint with the authorization code and, according to OIDC Core Section 3.1.3.3 Successful Token Response the response MUST include an ID Token.
So for either flow, that's how you initially get the ID Token, but how do you refresh it? OIDC Section 12: Using Refresh Tokens has the following statement about the Refresh Token Response:
Upon successful validation of the Refresh Token, the response body is the Token Response of Section 3.1.3.3 except that it might not contain an id_token.
It might not contain an ID Token and since there is no way specified to force it to include the ID token, you must assume that the response will not contain the ID Token. So technically there is no specified way to "refresh" an ID Token using a refresh token. Therefore, the only way to get a new ID Token is to re-authorize/authenticate the user by redirecting the user to the authorization endpoint and starting the implicit flow or authentication code flow as described above. The OIDC specification does add a prompt request parameter to the authorization request so the client can request that the authorization server not prompt the user with any UI, but the the redirect still has to happen.
If I understand correctly, according to this and the OpenID Connect Core 1.0 spec, the ID token itself can be stored in cookies as a mechanism to persist sessions, and sent with every authentication-requiring request to the Client. The Client can then verify the ID token either locally or through the Provider's verifier endpoint (if provided, like Google does). If the token is expired, it should make another auth request, except this time with prompt=none in the URL parameter. Also make sure to send the expired ID token in the id_token_hint parameter, otherwise the Provider may return an error.
So, it does seem natural for the ID Token to expire, but prompt=none ensures the new ID token can be obtained smoothly with no user intervention (unless of course the user is logged out of that OpenID).
It is the same intent: you can't use the id_token after it is expired. The main difference is that an id_token is a data structure and you won't need to call any servers or endpoints, as the information is encoded in the token itself. A regular access_token is usually an opaque artifact (like a GUID).
The consumer of the id_token must always verify the (time) validity of it.
I'm not 100% familiar with IS, but I would guess it is a convenience field. You should always check the exp claim.
Expiration is just one of the validations. id_tokens are also digitally signed and that is also a validation you must perform.
Refreshing a token means that you can use it again for requesting something from the authorization server (in this case the OP - the OpenID-Connect Provider) EVEN WHEN THE USER IS NOT LOGGED IN. You typically allow this for limited resources only, and only after the user has logged in and been authenticated at least once. The refresh tokens themselves should be also limited in time.
In OIDC implicit flow you call the Authorization endpoint,
and receive the ID token in the response along with all the scopes and in them all the claims info.
Subsequent calls to an API are meant to be done with code flow.
Implicit flow is meant to enable a javascript only or browser only app. Not an app that is interacting with a server.
So even if there was a way to "refresh" this token, you should not - security wise - allow it to live too long. It will be stolen and reused by unauthorized users impersonating the id. You should force a new login for that.
In code flow you call the OP's Authorization endpoint, and receive an Authorization code (also called an authorization token, or authcode for short). This should expire similar to the id_token that you received in implicit flow, for the same reasons and cannot and should not be renewed.
Your UI or app then call the OP's Token endpoint, and receives (sometimes after the user's further consent through a UI to allow use of their owned resources on the OP's server) both:
An id_token, for authentication - which should never be used again in server calls, except as a hint during logout, when its expiration is not important anymore, and so, for the reasons above should be let to expire, and never be refreshed.
An access_token - which later on, when calling an API, can be given to the OP's UserInfo endpoint. That will return the claims, and the API can authorize accordingly.
You can refresh this access_token, since it only tells the API what claims the user has, and what resources (by scopes and each scope's claims) the user agreed to give you. As explained above this is for allowing access even after the user is not logged in anymore. Of course you never wish to allow the id_token to be refreshed, because you don't want to allow impersonation without logging in.
I wanted to post this answer as a comment but since I haven't been very active on StackOverflow, I guess I'm posting it as an alternate answer.
You also use id_token as the id_token_hint when attempting to log the user out of a session http://openid.net/specs/openid-connect-session-1_0.html. I honestly don't think that it really matters if the id_token is expired at this point since you're only concerned about logging out a particular user.
TLDR;
Validate the ID token before trusting what it says.
More Details
What is intent of ID token expiry time in OpenID Connect?
The intent is to allow the client to validate the ID token, and the client must validate the ID token before operations that use the ID token's information.
From the OpenID Implicit Flow spec:
If any of the validation procedures defined in this document fail, any operations requiring the information that failed to correctly validate MUST be aborted and the information that failed to validate MUST NOT be used.
To corroborate that, Google's OpenID Connect documentation says this about ID token validation:
One thing that makes ID tokens useful is that fact that you can pass them around different components of your app. These components can use an ID token as a lightweight authentication mechanism authenticating the app and the user. But before you can use the information in the ID token or rely on it as an assertion that the user has authenticated, you must validate it.
So, if our client application is going to take some action based on the content of the ID token, then we must again validate the ID token.
Just share my journey. It's June, 2021. I'm writing this because I've stumbled into 3rd-party authentication business. I'm a veteran programmer but novice to security. In other words, all standards, spec, and terminologies are strangers, anybody can beat me in this field. Forgive me for not going by all the terms.
Cut to chase, I'm writing an Angular/Node app, so UI=Angular, API (API server)=Node/Express. Instead of creating my own Username/Password authentication, I'm turning to 3rd-party authentication, let them validate the genuineness of what the users claim they are. Here are two important guidebooks for me:
Angular Authentication With JSON Web Tokens (JWT): The Complete Guide
Eiji's Authenticate with a backend server
Combining No. 1 and angularx-social-login, I have the UI hooked up with Google, then attach idToken to API, vola! Following No. 2 using local library API can validate idToken, great!
Wait, idToken has exp expires in 1-hour. How do I refresh it?
My understanding is all I need is Google's authentication, I don't care what standard and version they use, but like others I just trust their authentication. Authentication is basically verify who they claim they are. Authorization is access control for what/where users can do/goto. The internal access control (allow users to do what) is not exposed to Google they have no idea of it. So accessToken should be out of the picture. Right?
I've spent days studying on how to refresh idToken now concluded Google does not recommend it nor angularx-social-login offers a way. In No. 2, Eiji has stated clearly:
Hence, solution for my situation is
use Google's authentication.
creates an own session management/timeout-rules at API after initial validation of idToken to mitigate exp.
preferably add this session data into cookie with res.cookie("SESSIONID", myOwnID, {httpOnly:true, secure:true});
For better protection, Eiji also recommends Cross Account Protection. Hope this will help someone!

Resources