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

[Question/Documentation]: Example showing how to call MS Graph API #40

Closed
JoeEager-VUMC-zz opened this issue Nov 11, 2021 · 19 comments
Closed
Assignees
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested

Comments

@JoeEager-VUMC-zz
Copy link

Was wondering if there were any examples of showing how to use the token to call the MS Graph API?

@JoeEager-VUMC-zz JoeEager-VUMC-zz added the enhancement New feature or request label Nov 11, 2021
@JonasKs
Copy link
Member

JonasKs commented Nov 12, 2021

Hi!

I’m afraid not. I still haven’t used the Graph API.

@JonasKs JonasKs added question Further information is requested and removed enhancement New feature or request labels Nov 13, 2021
@JonasKs JonasKs added the help wanted Extra attention is needed label Feb 9, 2022
@JonasKs
Copy link
Member

JonasKs commented Feb 9, 2022

If anyone has used the Graph API and would like to contribute to the documentation with a little tutorial, that would be greatly appreciated. 🚀

@JonasKs JonasKs added good first issue Good for newcomers documentation Improvements or additions to documentation labels Mar 30, 2022
@JonasKs JonasKs changed the title [Question] Example showing how to call MS Graph API [Question/Documentation]: Example showing how to call MS Graph API Mar 30, 2022
@u-iandono
Copy link

I got the following error when trying to call the Microsoft Graph API using the access token coming from the User object:

{'error': {'code': 'InvalidAuthenticationToken', 'message': 'Invalid x5t claim.', 'innerError': {'date': '2022-03-31T03:16:06', 'request-id': 'ee81c3f0-f21
d-44d6-a7bd-c91bdd102971', 'client-request-id': 'ee81c3f0-f21d-44d6-a7bd-c91bdd102971'}}}

After reading this: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md, I realized it's related to how the scopes work in AzureAD.

So I tried adding the User.Read scope to both the Backend and OpenAPI apps, and then only selecting this scope in the login pop-up in the OpenAPI docs. But I got the following response:

{
  "detail": "Unable to validate token"
}

And this is the error stack:

Invalid token. Error: Signature verification failed.
Traceback (most recent call last):
  File "c:\users\me\.virtualenvs\x-lru6flmw\lib\site-packages\jose\jws.py", line 262, in _verify_signature
    raise JWSSignatureError()
jose.exceptions.JWSSignatureError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\users\me\.virtualenvs\x-lru6flmw\lib\site-packages\jose\jwt.py", line 142, in decode
    payload = jws.verify(token, key, algorithms, verify=verify_signature)
  File "c:\users\me\.virtualenvs\x-lru6flmw\lib\site-packages\jose\jws.py", line 73, in verify
    _verify_signature(signing_input, header, signature, key, algorithms)
  File "c:\users\me\.virtualenvs\x-lru6flmw\lib\site-packages\jose\jws.py", line 264, in _verify_signature
    raise JWSError("Signature verification failed.")
jose.exceptions.JWSError: Signature verification failed.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "c:\users\me\.virtualenvs\x-lru6flmw\lib\site-packages\fastapi_azure_auth\auth.py", line 183, in __call__
    token = jwt.decode(
  File "c:\users\me\.virtualenvs\x-lru6flmw\lib\site-packages\jose\jwt.py", line 144, in decode
    raise JWTError(e)
jose.exceptions.JWTError: Signature verification failed.

I don't know how to proceed from this point 😅

@JonasKs
Copy link
Member

JonasKs commented Mar 31, 2022

Yeah, I've never actually tried this.

It seems from the link you sent that:

In the code snippet above, even though the user consents to both User.Read and api://<myCustomApiClientId>/My.Scope scopes, they will only receive an Access Token for MS Graph API, in accordance with per-resource-per-scope(s) principle. However, since they already consented to api://<myCustomApiClientId>/My.Scope, they can acquire an Access Token for that resource/scope silently later on.

I believe, that this means the same would work the opposite way, where if you selected your application scope (user_impersonation) first, and then User.Read, you could authenticate as normal. This access token would then have already been consented to read the graph API. You can then use this access token to fetch another access token, which you then can use for the Graph API.

I have a packed day today, but I'll see if I find some time tonight or this weekend. 😊

@JonasKs
Copy link
Member

JonasKs commented Apr 3, 2022

A token with the wrong audience (for Graph) will always be denied, and that's what you see when you get this error:

{
  "detail": "Unable to validate token"
}

Pasting the token into jwt.io will show you that the aud is 0000... and not your client ID, and must be denied by our token validation.

The token with the correct aud can not work for the Graph API, for the same reason. This is where the on-behalf-flow(OBO) comes in. As far as I can understand, we should use the scope api://{settings.APP_CLIENT_ID}/.default when prompting the user, and then fetch another token like this:

async with AsyncClient() as client:
    data = {
        'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'client_id': settings.APP_CLIENT_ID,
        'client_secret': settings.GRAPH_SECRET,
        'assertion': request.state.user.access_token,
        'scope': 'https://graph.microsoft.com/user.read',
        'requested_token_use': 'on_behalf_of'
    }
    response = await client.post('https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token', data=data)

with the secret created for the backend appreg.

However, this still gives me an error:

{'error': 'invalid_grant', 'error_description': "AADSTS65001: The user or administrator has not consented to use the application with ID '<application id>' named 'FastAPI-Azure-Auth-DemoProj'. Send an interactive authorization request for this user and resource.", 'error_codes': [65001], 'suberror': 'consent_required'}

which I don't understand, since I consent to that on login, and can confirm by checking the Enterprise Application for the OpenAPI appreg:

image

The token does not have the User.Read scope in it, even when using .default as the scope:
image

Any tips or tricks we should check out @h3rmanj ?

@JonasKs
Copy link
Member

JonasKs commented Apr 3, 2022

I also tried adding

	"knownClientApplications": [
		"00000003-0000-0000-c000-000000000000"
	],

to my manifests, but didn't help :/

@JonasKs
Copy link
Member

JonasKs commented Apr 3, 2022

I pushed a graph debug branch if someone want to look over the code and give it a shot themselves. I still haven't been able to make it work.

Click for instructions
  1. Copy demo_project/.env.example -> demo_project/.env
  2. Fill out the .env values
  3. poetry install to install dependencies
  4. uvicorn demo_project.main:app --reload --host localhost --port 8000 to run the project
  5. Navigate to localhost:8000/docs and click Authorize
  6. Scroll to the top and use the SingleTenant authentication method.
  7. Select the scope and leave client secret blank. Click authorize
  8. Sign in
  9. Call the hello-graph endpoint

To attempt different scopes, see azure_scheme in demoproject/api/dependencies.py.

@u-iandono
Copy link

u-iandono commented Apr 4, 2022

@JonasKs I got this result

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6IkNndDNnRDhidDYxZ18yUkpvZmZFSmhnMlRUZHhoa2E3Q3JLUVNMcWNGVjAiLCJhbGciOiJSUzI1NiIsIng1dCI6ImpTMVhvMU9XRGpfNTJ2YndHTmd2UU8yVnpNYyIsImtpZCI6ImpTM",
  "token_type": "Bearer",
  "scope": "profile openid email https://graph.microsoft.com/User.Read",
  "expires_in": 4431,
  "ext_expires_in": 4431
}

Does this mean success?

EDIT:

I tried calling the Microsoft Graph API with the token and, yeah, I got the result.
image

@JonasKs
Copy link
Member

JonasKs commented Apr 4, 2022

Using OBO? 🎉

How's your appregs set up? I'd love to make it work for me too so I can write docs for it (PRs welcome!😊)

Edit: I removed your access token from your message.

@u-iandono
Copy link

Using OBO? 🎉

How's your appregs set up? I'd love to make it work for me too so I can write docs for it

Yeah 🎊 , I only change this part from your code

response = await client.post('https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token', data=data)

I use the tenant ID of Backend API appregs instead of the OpenAPI

I think the rest of the configuration is the same.

image

Edit: I removed your access token from your message.

Thank you so much!

@JonasKs
Copy link
Member

JonasKs commented Apr 4, 2022

Alright, thank you! I don’t have that green check box after User.Read so I’ll have to look into that.

You’re saying your appregs are the exact same as my tutorial told you to set them up? 😊

@u-iandono
Copy link

Alright, thank you! I don’t have that green check box after User.Read so I’ll have to look into that.

Thank you!

You’re saying your appregs are the exact same as my tutorial told you to set them up? 😊

Yep! I only create new client secrets because I did not have it previously.

@JonasKs
Copy link
Member

JonasKs commented Apr 4, 2022

Awesome!

My status is not the same as yours:

image

@u-iandono
Copy link

Ah yeah, I think you're correct! I tried revoking the admin consent and I got the same error as you now:

{
  "error": "invalid_grant",
  "error_description": "AADSTS65001: The user or administrator has not consented to use the application with ID 'edd67d27-ce10-4252-8f5e-2d7b7a4bbe90' named 'BE'. Send an interactive authorization request for this user and resource.\r\nTrace ID: ca86ff3c-77b2-4874-a372-affd83d46500\r\nCorrelation ID: 71ed2675-cc1b-472a-a5fa-251b63134f98\r\nTimestamp: 2022-04-04 07:29:13Z",
  "error_codes": [
    65001
  ],
  "timestamp": "2022-04-04 07:29:13Z",
  "trace_id": "ca86ff3c-77b2-4874-a372-affd83d46500",
  "correlation_id": "71ed2675-cc1b-472a-a5fa-251b63134f98",
  "suberror": "consent_required"
}

@JonasKs
Copy link
Member

JonasKs commented Apr 4, 2022

Just got granted admin consent now and everything works here too!

Thanks for troubleshooting this with me! I'll make sure to add some helper functions and documentation on how this works.

@JonasKs JonasKs self-assigned this Apr 4, 2022
@h3rmanj
Copy link
Member

h3rmanj commented Apr 4, 2022

Hi!

Using delegated permissions should not require admin consent (unless specified in Azure AD). I tested a setup with .NET (should be the same for fastapi, the important thing is consent and app configuration), and got it to work with only user consent.

To make this work we need some configuration in Azure AD.

The Swagger app registrations needs the following permissions:

  • user_impersonation of the API app registration
  • email, offline_access, openid and profile from Microsoft Graph1

The API app registration needs to:

  • Expose a user_impersonation scope
  • User.Read permissions from Microsoft Graph (or any other delegated permission that doesn't require admin consent)
  • Add the Swagger app registrations Client ID to the knownClientApplications array in the manifest2

In Swagger, you have to configure the authentication request to get the .default scope of your API app registration (api://{CLIENT_ID}/.default).2

You can now use the OBO3 flow as expected.

Footnotes

  1. Delegated permissions to Microsoft Graph, the client application is in this case the Swagger app registration

  2. Gaining consent for the middle tier application 2

  3. Middle-tier access token request

@JonasKs
Copy link
Member

JonasKs commented Apr 4, 2022

Thank you @h3rmanj ❤️

Add the Swagger app registrations Client ID to the knownClientApplications array in the manifest

I initially thought I messed up this step, but I had both the swagger app client ID and the Graph client ID in there. So not sure why it didn't work for me yesterday. Maybe updating manifests are slow (like they can be with v1 -> v2 swap)? I never attempted again today before I got admin consent.

Anyway, on a fresh app registration everything worked as you described.

I'll write some docs. 🎉

JonasKs added a commit that referenced this issue Apr 4, 2022
  - #62: Tutorial on how to use Python to interact with your APIs
  - #61: Tutorial on how to access user attributes in your APIs
  - #45: Added a page that explains how to solve admin consent when
    signing in. Thank you @bkmetzler
  - #40: Add tutorial on how to set up and use Graph APIs using the On
    Behalf Flow. Thank you @u-iandono & @h3rmanj
- Add graph endpoint example
JonasKs added a commit that referenced this issue Apr 4, 2022
  - #62: Tutorial on how to use Python to interact with your APIs
  - #61: Tutorial on how to access user attributes in your APIs
  - #45: Added a page that explains how to solve admin consent when
    signing in. Thank you @bkmetzler
  - #40: Add tutorial on how to set up and use Graph APIs using the On
    Behalf Flow. Thank you @u-iandono & @h3rmanj
- Add graph endpoint example
@JonasKs JonasKs closed this as completed in 7c2d3bb Apr 4, 2022
@JonasKs
Copy link
Member

JonasKs commented Apr 4, 2022

Thank you so much, both of you!

I've added documentation here.

@u-iandono
Copy link

Thank you so much @JonasKs !

nikstuckenbrock pushed a commit to nikstuckenbrock/fastapi-azure-auth that referenced this issue Oct 16, 2023
  - Intility#62: Tutorial on how to use Python to interact with your APIs
  - Intility#61: Tutorial on how to access user attributes in your APIs
  - Intility#45: Added a page that explains how to solve admin consent when
    signing in. Thank you @bkmetzler
  - Intility#40: Add tutorial on how to set up and use Graph APIs using the On
    Behalf Flow. Thank you @u-iandono & @h3rmanj
- Add graph endpoint example
nikstuckenbrock pushed a commit to nikstuckenbrock/fastapi-azure-auth that referenced this issue Oct 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants