Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

example for web api token validation #147

Open
ctaggart opened this issue Jan 29, 2020 · 34 comments
Open

example for web api token validation #147

ctaggart opened this issue Jan 29, 2020 · 34 comments

Comments

@ctaggart
Copy link

I need to validate the identity using a Flask web API. It is a different flow than the ms-identity-python-webapp example. I think I just need to validate the jwt token and some of its values similar to what is done here with this code. Is this something that can be done with msal or am I better off using code like that?

Client credentials flow

@rayluo
Copy link
Collaborator

rayluo commented Jan 29, 2020

Hi Cameron, thank you for your valuable question! You clearly did your research. :-)

So, what you want is the token validation. And that is NOT the same as "client credentials flow". For what it's worth, client credentials flow is still about the token acquisition, not token validation.

Regardless, we do not currently have a sample for token validation. I'm marking this as an enhancement item here, and we will take this into consideration when next time we update our plan.

@ctaggart
Copy link
Author

They you Ray. Yes, token validation is what I'm looking for.

@ctaggart ctaggart changed the title example for web api of client credentials flow example for web api token validation Jan 29, 2020
@gwsampso
Copy link

gwsampso commented Feb 5, 2020

+1 for token example

@dsjijo
Copy link

dsjijo commented Mar 24, 2020

Hi ,

This is not bug , but needs a help on how to do this feature
to github
.
I have a web app (Flask ) A created by following this tutorial. Link
And I have another website B hosted in another host provider and trying to access above api from B.
I added CORS in A but want to impliment an oAuth2 in azure . how to do that?
I am looking a scenario where credentials prompt is not shown . like a deamon app it should be able to get the token.
I am new to all these. Hope you got what am i looking into.

@dsjijo
Copy link

dsjijo commented Mar 24, 2020

any example also would do. Thanks.

@dsjijo
Copy link

dsjijo commented Mar 25, 2020

Hi All
I have found a repository doing the same Link .

The token I was able to get through following this link and restructed my app like the secureFlaskApp.

Thanks for the help.

@eprigorodov
Copy link

eprigorodov commented Nov 5, 2020

A sufficient number of JWT validation checks is being performed in the msal.oauth2cli.oidc.decode_id_token(), which is called upon adding tokens into TokenCache: token_cache.py:137. But these checks do not include signature verification, [update: which is not necessary when obtaining tokens directly from the AAD server over SSL, e.g. via .acquire_token_by_authorization_code()].

Also, msal depends on pyjwt library, which contains API method for full JWT validation. The method requires AAD public key, so here is the way to call it [update: for ID tokens]:

  1. Load OpenID configuration from https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
  2. Use the jwks_uri endpoint to load AAD public keys (currently https://login.microsoftonline.com/common/discovery/v2.0/keys).
  3. Take the key that corresponds to "kid" field value of JWT header.
  4. Base64-decode the value of key's "x5c" field and decode it as X.509 certificate in DER format. Convert its public key part into PEM format.
  5. Call jwt.decode(itoken, public_key, audience=<client_id>), supplying client_id of your application, and catch exceptions that it can raise.
from base64 import b64decode
import jwt
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import requests

jwks_uri = 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
jwkeys = requests.get(jwks_uri).json()['keys']

token_key_id = jwt.get_unverified_header(token)['kid']
jwk = [key for key in jwkeys if key['kid'] == token_key_id][0]
der_cert = b64decode(jwk['x5c'][0])
cert = x509.load_der_x509_certificate(der_cert, default_backend())
public_key = cert.public_key()
pem_key = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)
token_claims = jwt.decode(token, pem_key, audience=client_id)

update: This method may fail for access tokens, because they might be issued for another audience (e.g. Microsoft Graph API) and signed with audience-specific key.

@rayluo
Copy link
Collaborator

rayluo commented Nov 5, 2020

Hi @eprigorodov , thank you for the code review on our existing implementation. We love this community voices. :-)

With regard to the topic you brought up above, it is actually a different topic than the current issue. The current issue is about Access Token validation, the topic you brought up is about ID Token validation. MSAL already performs ID token validation, we just validate it in a different-than-pyjwt way, but still specs-compliant.

Should you have follow-up question on ID token validation, please create ANOTHER issue for its subsequent discussion.

@cheslijones
Copy link

cheslijones commented Nov 7, 2020

Would be great to have functionality to verify access_token sent from the FE. It would seem like this functionality would make sense given the Angular and upcoming React libraries in @azure/msal-browser. Those access_token need to be validated by your API whether it is Express, Django/DRF, etc. somehow and it is not clear how to do this in this library's current state.

@eprigorodov
Copy link

@rayluo, thank you for pointing out, indeed I was looking for an issue about ID token validation and misread the actual subject.

@cheslijones, access tokens issued for Microsoft APIs are usually supposed to be opaque for the client and will be validated by the very endpoint service. Clients just send them as-is to the target API and process responses with possible validation errors. msal.TokenCache will take care about expiration and refresh, if used in a recommended way (see point 2. of the usage guide).

@cheslijones
Copy link

@eprigorodov It sounds like the library does not support my use case.

@eprigorodov
Copy link

@cheslijones The MSAL is a client authentication library. Server middleware libraries are listed here: https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-v2-libraries#microsoft-supported-server-middleware-libraries

For Django you can try authlib. It uses the same approach as the code in the comment above. JWT signature verification will work if access token is signed with one of "generic" Microsoft keys, the ones exposed via well-known OpenID configuration.

For that authorization request, token request and access token should include one of scopes defined for the web application itself (listed under "Scopes defined by this API" on the blade "Expose an API" of the Azure App Registration).

@GuiLuccaDev
Copy link

No caso procuro a mesma solução em relação a validação do token. Pois no caso quem vai conectar com o usuário é o front-end (web) e manter o usuário conectado para cada solicitação que ele fizer na api do back-end, mas ao passar para o back-end a requisição, é necessário que ele valide o token para saber se é daquele AD especifico usando o APPID e TENANTID, caso contrário essa conexão com o AD teria que ser feito pelo back-end, que não faz nenhum sentido, quando a idéia é fazer login usando credencias microsoft e manter o usuário conectado pelo tempo de expiração do access token.

@marchom
Copy link

marchom commented Dec 14, 2020

Of course, there are reference solutions out there as mentioned above. And yes, this is a client authentication library, but the recommended most secure flow is the authorization code flow, which requires this to be run on the server in order to have control of how you issue tokens to the clients (client secrets).

I think this makes it a very suitable place to include a def validate_token(self, audience,...) -> DecodedToken: somewhere in the class ClientApplication(object): which then can be included into any middleware, but then at least the implementation is right there for the use, and potential security or performance impacting bugs in an area as critical as the validation of the tokens (performed on all requests) is avoided in the multitude of servers using the authorization code flow (or any other implementation that requires the token acquisition and validation to happen in the same application).

I think including this feature in the library would be great for us users and will mitigate potential vulnerabilities of improper validation by everyone re-implementing reference solutions and making mistakes.

@guicarvalho
Copy link

Hello guys,

I have the same problem, @rayluo any evolution at that point?

@rayluo
Copy link
Collaborator

rayluo commented Feb 11, 2021

Sorry, not yet. :-(

@Priyanka9496
Copy link

hello guys,

I too have same problem, @rayluo any evolution?

@tjeerddie
Copy link

hey, I found a good example for the token validation that also uses the on behalf of flow to let the API communicate with the Microsoft API's.
https://github.com/Azure-Samples/ms-identity-python-on-behalf-of

would be awesome if msal could have the functionality for validation, it was a pain to figure out :3

@datasleek
Copy link

A sufficient number of JWT validation checks is being performed in the msal.oauth2cli.oidc.decode_id_token(), which is called upon adding tokens into TokenCache: token_cache.py:137. But these checks do not include signature verification, [update: which is not necessary when obtaining tokens directly from the AAD server over SSL, e.g. via .acquire_token_by_authorization_code()].

Also, msal depends on pyjwt library, which contains API method for full JWT validation. The method requires AAD public key, so here is the way to call it [update: for ID tokens]:

1. Load OpenID configuration from https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

2. Use the `jwks_uri` endpoint to load AAD public keys (currently https://login.microsoftonline.com/common/discovery/v2.0/keys).

3. Take the key that corresponds to `"kid"` field value of JWT header.

4. Base64-decode the value of key's `"x5c"` field and decode it as X.509 certificate in DER format. Convert its public key part into PEM format.

5. Call `jwt.decode(itoken, public_key, audience=<client_id>)`, supplying `client_id` of your application, and catch exceptions that it can raise.
from base64 import b64decode
import jwt
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import requests

jwks_uri = 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
jwkeys = requests.get(jwks_uri).json()['keys']

token_key_id = jwt.get_unverified_header(token)['kid']
jwk = [key for key in jwkeys if key['kid'] == token_key_id][0]
der_cert = b64decode(jwk['x5c'][0])
cert = x509.load_der_x509_certificate(der_cert, default_backend())
public_key = cert.public_key()
pem_key = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)
token_claims = jwt.decode(token, pem_key, audience=client_id)

update: This method may fail for access tokens, because they might be issued for another audience (e.g. Microsoft Graph API) and signed with audience-specific key.

Hi I am using your code to decode client side token given by teams to tab. I have also raised my query here : https://stackoverflow.com/questions/67401139/using-python-decode-client-side-token-fetched-by-microsoft-teams-and-given-to-ta

I am getting this error jwt.exceptions.InvalidAudienceError: Invalid audience

Is there anything that you can help me with? I used Application Id(when we register app in azure active directory) as client_id.

@datasleek
Copy link

datasleek commented May 5, 2021

A sufficient number of JWT validation checks is being performed in the msal.oauth2cli.oidc.decode_id_token(), which is called upon adding tokens into TokenCache: token_cache.py:137. But these checks do not include signature verification, [update: which is not necessary when obtaining tokens directly from the AAD server over SSL, e.g. via .acquire_token_by_authorization_code()].
Also, msal depends on pyjwt library, which contains API method for full JWT validation. The method requires AAD public key, so here is the way to call it [update: for ID tokens]:

1. Load OpenID configuration from https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

2. Use the `jwks_uri` endpoint to load AAD public keys (currently https://login.microsoftonline.com/common/discovery/v2.0/keys).

3. Take the key that corresponds to `"kid"` field value of JWT header.

4. Base64-decode the value of key's `"x5c"` field and decode it as X.509 certificate in DER format. Convert its public key part into PEM format.

5. Call `jwt.decode(itoken, public_key, audience=<client_id>)`, supplying `client_id` of your application, and catch exceptions that it can raise.
from base64 import b64decode
import jwt
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import requests

jwks_uri = 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
jwkeys = requests.get(jwks_uri).json()['keys']

token_key_id = jwt.get_unverified_header(token)['kid']
jwk = [key for key in jwkeys if key['kid'] == token_key_id][0]
der_cert = b64decode(jwk['x5c'][0])
cert = x509.load_der_x509_certificate(der_cert, default_backend())
public_key = cert.public_key()
pem_key = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)
token_claims = jwt.decode(token, pem_key, audience=client_id)

update: This method may fail for access tokens, because they might be issued for another audience (e.g. Microsoft Graph API) and signed with audience-specific key.

Hi I am using your code to decode client side token given by teams to tab. I have also raised my query here : https://stackoverflow.com/questions/67401139/using-python-decode-client-side-token-fetched-by-microsoft-teams-and-given-to-ta

I am getting this error jwt.exceptions.InvalidAudienceError: Invalid audience

Is there anything that you can help me with? I used Application Id(when we register app in azure active directory) as client_id.

Hi,

I decoded the token again in jwt.ms and find the aud paramter and used that value as audience to decode the token_claims again. It was successful.

@eprigorodov
Copy link

eprigorodov commented May 5, 2021

Hi @datasleek, as per documentation, the audience "aud" claim in ID token is either Application (client) ID or Application ID URI, e.g.:

'3513283e-1abe-420c-8de0-7415d2d26ae0'
'api://3513283e-1abe-420c-8de0-7415d2d26ae0'

Application ID URI can also be set in the Azure Portal interface, on the "Expose an API" blade of Application Registration object.

The rules for audience claims in access tokens are more complex, they depend on requested scopes. For example, tokens issued for Microsoft Graph scopes may conatin a magical audience OID "00000003-0000-0000-c000-000000000000".

It is possible also to turn off audience verification in jwt: jwt.decode(id_token, key, options={'verify_aud': False}).

@datasleek
Copy link

Hi @datasleek, as per documentation, the audience "aud" claim in ID token is either Application (client) ID or Application ID URI, e.g.:

'3513283e-1abe-420c-8de0-7415d2d26ae0'
'api://3513283e-1abe-420c-8de0-7415d2d26ae0'

Application ID URI can also be set in the Azure Portal interface, on the "Expose an API" blade of Application Registration object.

The rules for audience claims in access tokens are more complex, they depend on requested scopes. For example, tokens issued for Microsoft Graph scopes may conatin a magical audience OID "00000003-0000-0000-c000-000000000000".

It is possible also to turn off audience verification in jwt: jwt.decode(id_token, key, options={'verify_aud': False}).

Hi @eprigorodov ,

I apologise for late reply. Your code helped me to successfully decode the clientSideToken fetched by microsoft teams from AAD . Regarding aud yes you are correct. I also got help about the same on stackoverflow. Thanks for your reply and for the code.

@doverk96
Copy link

doverk96 commented Jun 10, 2021

Hi @datasleek, as per documentation, the audience "aud" claim in ID token is either Application (client) ID or Application ID URI, e.g.:

'3513283e-1abe-420c-8de0-7415d2d26ae0'
'api://3513283e-1abe-420c-8de0-7415d2d26ae0'

Application ID URI can also be set in the Azure Portal interface, on the "Expose an API" blade of Application Registration object.

The rules for audience claims in access tokens are more complex, they depend on requested scopes. For example, tokens issued for Microsoft Graph scopes may conatin a magical audience OID "00000003-0000-0000-c000-000000000000".

It is possible also to turn off audience verification in jwt: jwt.decode(id_token, key, options={'verify_aud': False}).

Hi @eprigorodev
I used options dict with access_token but it is not working. Hope key in above method is the public key.
Also, if we use jwt.io to check access_token, there also its signature is invalid. While for the id_token, it gives signature verified.
May be access_token are not to be dealt this way ? Please suggest solution for access_token.

@RamunasAdamonis
Copy link

For anyone else searching for this, I have found this example: https://github.com/ivangeorgiev/gems/tree/main/src/python-azure-ad-token-validate which validates access token.

@robteeuwen
Copy link

@rayluo, is there currently a plan to add token validation to this library? I understand it's not necessary to validate tokens for the graph API. However, I'm currently using this library to obtain tokens for my own API, by setting the scopes to point to my app registration id. The access token that my client app obtains is subsequently sent along with requests to the API, so I need to validate it in that API. I'm working with both Azure AD and B2C because my organization supports different login methods, and I found out those tokens need to be validated differently (the JWKS are different). I implemented my own code to validate the tokens, but it was quite difficult to figure out how to do that, and the MS documentation wasn't helpful in that regard. It would be great if this library could be used server-side to handle signature validation.

Is there any way I can contribute to such a feature?

I think the previously mentioned examples are helpful for AD validation. For B2C, I found this article helpful: https://robertoprevato.github.io/Validating-JWT-Bearer-tokens-from-Azure-AD-in-Python/. For anyone using Fast API, I recommend this library: https://github.com/Intility/fastapi-azure-auth

@rayluo
Copy link
Collaborator

rayluo commented Jan 14, 2022

I implemented my own code to validate the tokens, but ...
Is there any way I can contribute to such a feature?

Feel free to share a link to your implementation, so that we (Microsoft and the entire community) can utilize it.

We have not decided whether token validation feature should be part of this MSAL library, but it doesn't harm to consolidate some working code as a sample. In fact, some other Microsoft libraries/components were initially started as a sample, and evolve from there.

@bgavrilMS
Copy link
Member

CC @jennyf19 @jmprieur for token validation requests in PY

@pschiager
Copy link

pschiager commented Oct 26, 2023

This repo from MS may be useful for someone reading this in search of a FastAPI plugin or a generic reference solution to validate access tokens. It includes a decorator function (one liner above the method serving an api endpoint) for FastAPI, but the source code is similar to other references like mentioned above.

A few thoughts on that reference:

  1. Consider using the fork containing a not yet merged PR adding a missing return statement that helps debugging.
  2. The other PR regarding multi-tenancy support may or may not have security implications.
  3. Something that are mentioned other places and that may help debugging, is that an azure app registration may specify the token version, otherwise it will default to 1. You'll find this in Azure/App Reg//Manifest "accessTokenAcceptedVersion" which can be set to null/1/2. This is relevant for debugging errors in validating audience/issuer as the expected format varies with token version, as seen in auth_service.

@rayluo How would you recommend approaching this now for access token validation in a custom api? Using something from msal-python, a reference implementation, or authlib/pyjwt etc ?

@cutesweetpudding
Copy link

cutesweetpudding commented Jan 11, 2024

This drives me crazy, any helps appreciated.
X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk
is from my token header kid field.

unverified_header = jwt.get_unverified_header(token)         
print(unverified_header["kid"])

T1St-dLTvyWRgxB_676u8krXS-I
5B3nRxtQ7ji8eNDc3Fy05Kf97ZE
fwNs8F_h9KrHMA_OcTP1pGFWwyc
whLKSpgJ1osmGxXH6PbiDyBjoxE
jCScSBGaA6xAieLw3-sAuvjRM_0

from kid field of below
jsonurl = urlopen("https://login.microsoftonline.com/common/discovery/v2.0/keys");

They are totally different in length, none of them match.

jsonurl = urlopen("https://login.microsoftonline.com/440bfb1c-65d8-489c-a830-f1a118d83066/discovery/keys?p=B2C_1_user_signin_signup")
I also tried this, since I am using custom policy, I can't fetch any keys since it return 400X

@bgavrilMS
Copy link
Member

bgavrilMS commented Jan 11, 2024

@cutesweetpudding - the jwks_uri is found by going to the well-known metadata endpoint of the identity provider. This is typically the authority + ".well-known/openid-configuration". For example

https://login.microsoftonline.com/common/.well-known/openid-configuration

@cutesweetpudding
Copy link

@cutesweetpudding - the jwks_uri is found by going to the well-known metadata endpoint of the identity provider. This is typically the authority + ".well-known/openid-configuration". For example

https://login.microsoftonline.com/common/.well-known/openid-configuration

Thanks for replying, the issue I have is I can't find the signing key for my access token. I think my issues are more likely associated with multi tenants and user flow, I read documents it says I can't use common signing keys, I have to specify a custom signing key? I didn't do it obviously. So where can I find the signning key or how I can configure a custom signning key.

@eprigorodov
Copy link

eprigorodov commented Jan 12, 2024

@cutesweetpudding, what is the token scope?
if token was issued for the application (scope is "api://<application_id>"), then its public key should be discoverable,
but if token was issued for accessing MS Graph (scope contains Graph permissions or is "/.default"), then only Graph itself can verify it, token key is not published at any known location

@bgavrilMS
Copy link
Member

bgavrilMS commented Jan 12, 2024

@eprigorodov - afaik all Bearer tokens are signed by the token issuer (Identity Provider). The Identity Provider's public key is publicly available. Maybe the scenario you are thinking of is JWEs (encrypted JWT)- in this case the JWT payload is encrypted with a key that only the resource and the Identity Provider share. This prevents clients from decoding the token and accessing private information from the claims.

@cutesweetpudding - I believe the Identity Provider is B2C, because only B2C has the concept of "policies". There aren't multiple tenants in B2C and "common" isn't a thing.

Here are the details from one of our integration tests:

Authority: https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth
Token... notice the issuer doesn't have the policy... that's in the tfp claim

image

The OIDC document is at:
https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/B2C_1_ROPC_Auth/.well-known/openid-configuration

and the jwks document is at:
https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/b2c_1_ropc_auth/discovery/keys

PS: The "common" tenant is a concept that is specific to AAD / for "Work and School" accounts. A user can be a guest in another tenant and "common" means "let AAD figure out the user's home tenant".

@bgavrilMS
Copy link
Member

Here's my simplistic code that shows how to validate the signature of the token above.

Note that this is not production ready code and more checks probably need to happen for an app to trust this token. This is just to show the where to get the public key for a B2C authority.

import json
from urllib.request import urlopen
import jwt

token = "TOKEN_GOES_HERE"
key_url = "https://msidlabb2c.b2clogin.com/msidlabb2c.onmicrosoft.com/b2c_1_ropc_auth/discovery/keys"

jwks_client = jwt.PyJWKClient(key_url)
signing_key = jwks_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
     token,
     signing_key.key,
     algorithms=["RS256"],
     audience="2ae335cd-5ec9-4eee-8148-58be9e8020c9", # This is your web api's Application ID (you get it from Azure portal)     
 )

print(payload)
    

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests