Search code examples
reactjssingle-sign-onfastapiokta

SSO using Okta with React (typescript) and FastAPI (python)


I’ve implemented Okta authorization on frontend (React) using @okta/okta-react and @okta/okta-auth-js packages.

On the Okta side I created application that uses “OIDC - OpenID Connect” Sign-in method and has “Single-Page Application” Application type.

For python (backend) I can use okta-jwt-verifier-python package but I don’t know how to build whole flow to have an access to user info on backend.

Could you help me and write, what are the next steps? How properly pass auth info (tokens) from frontend and how to validate it in backend? How can I get user info on backend by Okta token? Do I need to create one more app in Okta for backend?

I tried to create middleware in FastAPI but it looks like workaround, and it won't work if I decided to test API using build-in swagger interface


Solution

  • I've solved my problem using okta-jwt-verifier python package. It also works from web interface of FastAPI, but you should use valid Bearer token.

    I created new file (auth_utils.py) with next code to implement validation:

    # The goal of this file is to check whether the request is authorized or not [ verification of the protected route]
    from fastapi import Request, HTTPException
    from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
    from okta_jwt_verifier import BaseJWTVerifier
    from okta_jwt_verifier.exceptions import JWTValidationException
    
    
    class JWTBearer(HTTPBearer):
        def __init__(self, auto_error: bool = True):
            super(JWTBearer, self).__init__(auto_error=auto_error)
    
        async def __call__(self, request: Request):
            credentials: HTTPAuthorizationCredentials = await super(JWTBearer, self).__call__(request)
            if credentials:
                if not credentials.scheme == "Bearer":
                    raise HTTPException(status_code=403, detail="Invalid authentication scheme.")
    
                try:
                    await self.verify_jwt(credentials.credentials)
                except JWTValidationException as e:
                    message = ''.join(e.args)
                    raise HTTPException(status_code=403, detail=message)
    
                return credentials.credentials
            else:
                raise HTTPException(status_code=403, detail="Invalid authorization code.")
    
        async def verify_jwt(self, jwtoken: str):
            jwt_verifier = BaseJWTVerifier('https://your-issuer-domain.okta.com',
                                           'your_client_id',
                                           'api://default')
            await jwt_verifier.verify_access_token(jwtoken)
    
    

    And use JWTBearer class in the next way for any route that you want to protect:

    @app.get("/protected/endpoint", dependencies=[Depends(JWTBearer())])
    async def protected_endpoint():
        return "It will work only with valid JWT token ;)"
    

    But pay attention you should pass Authorization header from your frontend app!