dudil / fastapi_msal

A FastAPI Plug-In to support authentication authorization using the Microsoft Authentication Library (MSAL)
MIT License
43 stars 20 forks source link

Document how to get a token for accessing the API via curl #10

Closed philipsd6 closed 11 months ago

philipsd6 commented 3 years ago

Is your feature request related to a problem? Please describe. The authentication flow from the Swagger Docs UI is clear, but what isn't discussed is how a user could use the API directly from curl, without reusing the token retrieved from the Swagger Docs authentication flow.

Describe the solution you'd like A session-less/Swagger Doc-less way to use an API I protect with msal_auth.scheme. For example:

curl http://localhost:8000/users/me  # 401 Unauthorized: {"detail":"Not authenticated"}

So we need to login:

curl -v http://localhost:8000/login  # 307 Temporary Redirect
# location: https://login.microsoftonline.com/.../oauth2/v2.0/authorize?...&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftoken

So I ensure http://localhost:8000/token is added to my app registrations allowed redirect URIs. Then I open the location URI in my browser and go through the Azure authentication flow in the portal, just as with the Swagger Docs UI auth flow. It should redirect to my /token endpoint which will return a token for me to use later:

curl -H @auth.header.txt http://localhost:8000/users/me

Instead I get {"detail":"Authentication Error"} from my local API.

Describe alternatives you've considered I've tried to look into service principals, and adding an App Role "Can Invoke my API" but I cannot actually set those up without tenant admin access. I should be able to accomplish my goals without requiring Admin access.

Additional context Going a completely different direction, ideally, we should be able to protect our API with MSAL, but then internally once authenticated, be able to generate granular tokens for use via Curl, etc... i.e. a user might have admin access to the API via roles returned from MSAL, but then once logged into the local API, they should be able to generate a less-privileged token for use in their scripts. I have no idea how one might set that up though.

martin-greentrax commented 2 years ago

Could you progress on this issue? I am also trying to automate retrieval of a token

ejsyx commented 2 years ago

Session-less or bearer only

Your use case calls for a library that supports bearer only authentication. The underlying microsoft package msal is only intended to be used for authentication session management. This is, doing the initial login and then relying on a (cookie) session. Best I can recommend for session-less use case is https://github.com/busykoala/fastapi-opa. If you don't need OPA you can still copy the openid connect part.

Please don't use this package in a bearer only way it's not secure!

python MSAL does not do ANY cryptographic verification, I'm not making this up. See this issue about a reported vulnerability in pyJWT (a python package that can be used to cryptographically verify a JWT token) The response from MSAL authers was they don't use pyJWT for decoding.

Instead MSAL establishes trust from directly receiving the token from the identity provider and trusting the response came from a legitimate server by enforcing https.

I'm making this comment based on the README example:

@app.get("/users/me", response_model=UserInfo, response_model_exclude_none=True, response_model_by_alias=False)
async def read_users_me(current_user: UserInfo = Depends(msal_auth.scheme)) -> UserInfo:
    return current_user

SO to answer your initial question, any bearer token that matches you client_id, issuer and is not expired (time wise) will work, but you can easily fake that:

curl --location --request GET 'localhost:5000/users/me' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqa3lOdnRvbi1tOVludDN0dnFJSDdhV3dKb0xpN0VJLUFnNklSLWxHbWxZIn0.eyJleHAiOjE2NjA0OTkxMjMsImlhdCI6MTY2MDQ5OTA2MywiYXV0aF90aW1lIjoxNjYwNDk5MDYyLCJqdGkiOiIxNTJkYzEwMC02OGI2LTRkZDItODM5YS1kZjFlNjYxNWQzN2IiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsImF1ZCI6InNlY3VyaXR5LWFkbWluLWNvbnNvbGUiLCJzdWIiOiI3Y2U4NzE4ZS0wOTdkLTQ0NGYtYjhmZC1jNmY4MGI1YmVmYzkiLCJ0eXAiOiJJRCIsImF6cCI6InNlY3VyaXR5LWFkbWluLWNvbnNvbGUiLCJub25jZSI6ImYyYmZjNjFiLWExNGYtNDE0My04MDY1LWE1YWY5OGZjZWM2ZiIsInNlc3Npb25fc3RhdGUiOiI4ZDFkNjliYy0xOTVlLTRmOWMtYmE2Yy1mNmFhODRiOTRhMTYiLCJhdF9oYXNoIjoiWXFSaU5KblVKWEhwRFFYU0lOd1JqdyIsImFjciI6IjEiLCJzaWQiOiI4ZDFkNjliYy0xOTVlLTRmOWMtYmE2Yy1mNmFhODRiOTRhMTYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIn0.qE_MbnVxCo17k06zzGOoMWo2WY1NfJt9vefWLh_7umLVbIfXlp260clTs2fifvZHlsiHpHmWt-A8wbNVOberHQ5q3kqcTKAC889sRtSrucdXbvXTrDAt9pxcWO1tS60TQTG8Kl7OAqEC6Wf0_QhQMF1FM3jsFpBtTQdpDtIpowm0BeON19eKaSB-mSo3MejAH1U7D_4f5LP51dU1Nnp3fCGc-dXr6fLXvSmVN5ATEW9jtLi1oBrtvqptr24R0c5XNhdi2aFsEzdtjMEcxKWO53nfCsPu0tzly8ZNSuVl4wZPXO076y1TX-2UVnAtf7K_qp-ojMHic23ozMJN3Lyw-Q'

afbeelding

dudil commented 2 years ago

Session-less or bearer only

Your use case calls for a library that supports bearer only authentication. The underlying microsoft package msal is only intended to be used for authentication session management. This is, doing the initial login and then relying on a (cookie) session. Best I can recommend for session-less use case is https://github.com/busykoala/fastapi-opa. If you don't need OPA you can still copy the openid connect part.

Please don't use this package in a bearer only way it's not secure!

python MSAL does not do ANY cryptographic verification, I'm not making this up. See this issue about a reported vulnerability in pyJWT (a python package that can be used to cryptographically verify a JWT token) The response from MSAL authers was they don't use pyJWT for decoding.

Instead MSAL establishes trust from directly receiving the token from the identity provider and trusting the response came from a legitimate server by enforcing https.

I'm making this comment based on the README example:

@app.get("/users/me", response_model=UserInfo, response_model_exclude_none=True, response_model_by_alias=False)
async def read_users_me(current_user: UserInfo = Depends(msal_auth.scheme)) -> UserInfo:
    return current_user

SO to answer your initial question, any bearer token that matches you client_id, issuer and is not expired (time wise) will work, but you can easily fake that:

curl --location --request GET 'localhost:5000/users/me' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqa3lOdnRvbi1tOVludDN0dnFJSDdhV3dKb0xpN0VJLUFnNklSLWxHbWxZIn0.eyJleHAiOjE2NjA0OTkxMjMsImlhdCI6MTY2MDQ5OTA2MywiYXV0aF90aW1lIjoxNjYwNDk5MDYyLCJqdGkiOiIxNTJkYzEwMC02OGI2LTRkZDItODM5YS1kZjFlNjYxNWQzN2IiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsImF1ZCI6InNlY3VyaXR5LWFkbWluLWNvbnNvbGUiLCJzdWIiOiI3Y2U4NzE4ZS0wOTdkLTQ0NGYtYjhmZC1jNmY4MGI1YmVmYzkiLCJ0eXAiOiJJRCIsImF6cCI6InNlY3VyaXR5LWFkbWluLWNvbnNvbGUiLCJub25jZSI6ImYyYmZjNjFiLWExNGYtNDE0My04MDY1LWE1YWY5OGZjZWM2ZiIsInNlc3Npb25fc3RhdGUiOiI4ZDFkNjliYy0xOTVlLTRmOWMtYmE2Yy1mNmFhODRiOTRhMTYiLCJhdF9oYXNoIjoiWXFSaU5KblVKWEhwRFFYU0lOd1JqdyIsImFjciI6IjEiLCJzaWQiOiI4ZDFkNjliYy0xOTVlLTRmOWMtYmE2Yy1mNmFhODRiOTRhMTYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIn0.qE_MbnVxCo17k06zzGOoMWo2WY1NfJt9vefWLh_7umLVbIfXlp260clTs2fifvZHlsiHpHmWt-A8wbNVOberHQ5q3kqcTKAC889sRtSrucdXbvXTrDAt9pxcWO1tS60TQTG8Kl7OAqEC6Wf0_QhQMF1FM3jsFpBtTQdpDtIpowm0BeON19eKaSB-mSo3MejAH1U7D_4f5LP51dU1Nnp3fCGc-dXr6fLXvSmVN5ATEW9jtLi1oBrtvqptr24R0c5XNhdi2aFsEzdtjMEcxKWO53nfCsPu0tzly8ZNSuVl4wZPXO076y1TX-2UVnAtf7K_qp-ojMHic23ozMJN3Lyw-Q'

afbeelding

Totally Agree! I wasn't aware this is the initial request, do not mix between the MS authentication to your own website authentication. You could use both, but each should have its own session / key. The very simple example provided is missing the local cache for "session-less" server (but still session full platform).

dudil commented 11 months ago

Hi @philipsd6 , @martin-greentrax , @ejsyx I had just release a new version which will allow you to work that out as long as you are able to keep the same session token in your requests. The new version will fallback into reading the token from the session manager if one is not supplied by the bearer header. I hope this change will assist with your workflow. I'm closing this issue but feel free to open a new one if this one doesn't work out as you requested.