tomasvotava / fastapi-sso

FastAPI plugin to enable SSO to most common providers (such as Facebook login, Google login and login via Microsoft Office 365 Account)
https://tomasvotava.github.io/fastapi-sso/
MIT License
302 stars 47 forks source link

missing "mail" in response with Microsoft SSO #81

Closed arribatec-cloud-1 closed 9 months ago

arribatec-cloud-1 commented 10 months ago

I have set up an application in Azure with credentials.

When I try to log in using said credentials as per the examples, the call fails with a missing key error:


ERROR: KeyError('mail')
Traceback (most recent call last):
  File "/whatever/routes/sso_microsoft.py", line 54, in microsoft_callback
    user = await microsoft_sso.verify_and_process(request)
  File "/usr/local/lib/python3.9/site-packages/fastapi_sso/sso/base.py", line 212, in verify_and_process
    return await self.process_login(
  File "/usr/local/lib/python3.9/site-packages/fastapi_sso/sso/base.py", line 292, in process_login
    return await self.openid_from_response(content)
  File "/usr/local/lib/python3.9/site-packages/fastapi_sso/sso/microsoft.py", line 45, in openid_from_response
    return OpenID(email=response["mail"], display_name=response["displayName"], provider=cls.provider)
KeyError: 'mail'

The code looks like this:


from fastapi import APIRouter, Depends
from fastapi_sso.sso.microsoft import MicrosoftSSO
from starlette.requests import Request
import logging
import os
import pprint

logger = logging.getLogger(__name__)

allow_insecure_http = ("1" == os.environ.get("OAUTHLIB_INSECURE_TRANSPORT", "0"))

# documentation https://pypi.org/project/fastapi-sso/

sso_microsoft_route = APIRouter(
      prefix="/sso/microsoft"
    , tags = ["sso"]
    #,dependencies=[Depends(get_token_header)]
    , responses={404: {"description": "Not found"}}
)

MICROSOFT_SSO_DEBUG = os.environ.get("MICROSOFT_SSO_DEBUG")
MICROSOFT_SSO_REDIRECT_URL = os.environ.get("MICROSOFT_SSO_REDIRECT_BASE_URL")
MICROSOFT_SSO_TENANT = os.environ.get("MICROSOFT_SSO_TENANT")
MICROSOFT_SSO_CLIENT_ID = os.environ.get("MICROSOFT_SSO_CLIENT_ID")
MICROSOFT_SSO_CLIENT_SECRET = os.environ.get("MICROSOFT_SSO_CLIENT_SECRET")

if MICROSOFT_SSO_DEBUG:
    logger.info(f"  MICROSOFT_SSO_REDIRECT_URL: {MICROSOFT_SSO_REDIRECT_URL}")
    logger.info(f"        MICROSOFT_SSO_TENANT: {MICROSOFT_SSO_TENANT}")
    logger.info(f"     MICROSOFT_SSO_CLIENT_ID: {MICROSOFT_SSO_CLIENT_ID}")
    logger.info(f" MICROSOFT_SSO_CLIENT_SECRET: {MICROSOFT_SSO_CLIENT_SECRET}")

microsoft_sso = MicrosoftSSO(
      client_id = MICROSOFT_SSO_CLIENT_ID
    , client_secret = MICROSOFT_SSO_CLIENT_SECRET
    , tenant = MICROSOFT_SSO_TENANT
    , allow_insecure_http = allow_insecure_http
    , scope = ["openid"]
)

@sso_microsoft_route.get("/login")
async def microsoft_login(request: Request):
    with microsoft_sso:
        return await microsoft_sso.get_login_redirect(redirect_uri = request.url_for("microsoft_callback"))

@sso_microsoft_route.get("/callback")
async def microsoft_callback(request: Request):
    user = None
    with microsoft_sso:
        try:
            user = await microsoft_sso.verify_and_process(request)
        except Exception as e:
            logger.exception(f"ERROR: {pprint.pformat(e)}")
    if not user:
        logger.warning("NO USER")
        return None
    return {
        "id": user.get("id"),
        "picture": user.get("picture"),
        "display_name": user.get("display_name"),
        "email": user.get("email"),
        "provider": user.get("provider"),
    }
arribatec-cloud-1 commented 9 months ago

I investigated this further and .... well let's just say MicrosoftSSO has no error handling what-so-ever. I might submit a PR at some point if I get it to work.

If you need a patch straight away, put this in the top of MicrosoftSSO.openid_from_response():

from fastapi_sso.sso.base SSOLoginError
error = response.get("error")
        if error:
            raise SSOLoginError(401, f"Error '{pprint.pformat(error)}' returned from Microsoft")
tomasvotava commented 9 months ago

I believe some tenants require to ask for email scope directly, it is now a default in 0.8.0 https://github.com/tomasvotava/fastapi-sso/releases/tag/0.8.0

Could you test if this resolves the problem for you?

arribatec-cloud-1 commented 9 months ago

I did not test your suggestion, but it is aligning well with what I found what worked for me in the end; to omit the scope parameter from MicrosoftSSO constructor altogether. I used to have it set to ["openid"] which would override the default of ["openid", "User.Read"]. It could also just be luck/timing of the 10 times I created and changed my application settings and credentials inside Azure...

I guess just providing an example/documentation would solve this issue.

Anyways, thanks for looking at my issue!

Lennart Rolland Senior Cloud Consultant @.*** +4797688353


From: Tomas Votava @.> Sent: Friday, November 24, 2023 6:36 PM To: tomasvotava/fastapi-sso @.> Cc: Lennart Rolland @.>; Author @.> Subject: Re: [tomasvotava/fastapi-sso] missing "mail" in response with Microsoft SSO (Issue #81)

You don't often get email from @.*** Learn why this is importanthttps://aka.ms/LearnAboutSenderIdentification

I believe some tenants require to ask for email scope directly, it is now a default in 0.8.0 https://github.com/tomasvotava/fastapi-sso/releases/tag/0.8.0

Could you test if this resolves the problem for you?

— Reply to this email directly, view it on GitHubhttps://github.com/tomasvotava/fastapi-sso/issues/81#issuecomment-1825949756, or unsubscribehttps://github.com/notifications/unsubscribe-auth/A5HPLK4KR2B5SQL5EGKA6DTYGDLKDAVCNFSM6AAAAAA6PLOXVKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMRVHE2DSNZVGY. You are receiving this because you authored the thread.Message ID: @.***>

tomasvotava commented 9 months ago

That's a good idea, I've added a simple post to guide users who struggle with this as well, thanks!

https://tomasvotava.github.io/fastapi-sso/how-to-guides/key-error/