After reading the documentation I am struggling to understand how to use Authlib to implement Authorize Code Flow for an OpenID Connect provider. After reading the documentation I have had a go at implementing the following code listed below.

The /login endpoint uses authlib to redirect to authorization of Identity Provider, in this case Cognito. This redirects to /aws_cognito_redirect which I have currently implemented myself to resolve the code challenge to retrieve tokens.

My questions are:

  • How to use authlib to also resolve the code challenge instead of implementing this part myself?
  • Does Authlib provide functionality to return token(s) in HTTP Only cookie and verify tokens in subsequent requests containing the cookie? For example, does Authlib allow an endpoint to be decorated/marked to as protected, in which case it will verify the tokens in HTTP Only cookie?

Update

After inspecting the source code I eventually figured out how to resolve the code challenge using Authlib with FastAPI. The source code is included at the end of this question.

I am leaving the question open since the second part remains unanswered. Currently, this question suggests that it is possible to use ResourceProtector class that would do what I need. However, that has a parse_request_authorization method that inspects the Authorisation header of a request for a bearer token. So...I am assuming the approach is to subclass ResourceProtector class and override this method to inspect request for HTTP only cookie and extract the JWT contained within for verification?? Is this feature implemented and provided by Authlib?

Alternatively, also investigating to see if I can integrate fastapi-login to achieve this functionality.

Appendix: Source Code

Initial Source Code With Custom Implementation For Resolving Code Challenge

import base64
from functools import lru_cache

import httpx
from authlib.integrations.starlette_client import OAuth
from fastapi import Depends, FastAPI, Request, Response
from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware

from . import config


@lru_cache()
def get_settings() -> config.Settings:
    """Create config settings instance encapsulating app config."""

    return config.Settings()


def get_auth_base_url(region: str, userpool_id: str) -> str:
    # base_url = "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_QqNgzdtT5"

    base_url = "https://cognito-idp." + region + ".amazonaws.com/" + userpool_id

    return base_url


app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="secretly")

oauth = OAuth()
oauth.register(
    "cognito",
    client_id=get_settings().client_id,
    client_secret=get_settings().client_secret,
    server_metadata_url=get_auth_base_url(
        get_settings().region, get_settings().userpool_id
    )
    + "/.well-known/openid-configuration",
    client_kwargs={"scope": "openid email"},
)


def encode_auth_header(client_id: str, client_secret: str) -> str:
    """Encode client id and secret as base64 client_id:client_secret."""

    secret = base64.b64encode(
        bytes(client_id, "utf-8") + b":" + bytes(client_secret, "utf-8")
    )

    return "Basic " + secret.decode()


@app.get("/login")
async def login(request: Request):
    """Redirect to /aws_cognito_redirect endpoint."""

    cognito = oauth.create_client("cognito")
    redirect_uri = request.url_for("read_code_challenge")

    return await cognito.authorize_redirect(request, redirect_uri)


@app.get("/aws_cognito_redirect")
async def read_code_challenge(
    request: Request,
    response: Response,
    settings: config.Settings = Depends(get_settings),
):
    """Retrieve tokens from oauth2/token endpoint and return session cookie."""

    code = request.query_params["code"]
    print("/aws_cognito_redirect received code := ", code)

    auth_secret = encode_auth_header(settings.client_id, settings.client_secret)

    headers = {"Authorization": auth_secret}
    print("Authorization:" + str(headers["Authorization"]))

    payload = {
        "client_id": settings.client_id,
        "code": code,
        "grant_type": "authorization_code",
        "redirect_uri": settings.redirect_uri,
    }

    token_url = (
        "https://"
        + settings.domain
        + ".auth."
        + settings.region
        + ".amazoncognito.com/oauth2/token"
    )

    async with httpx.AsyncClient() as client:
        tokens = await client.post(
            token_url,
            data=payload,
            headers=headers,
        )
        tokens.raise_for_status()

        print("Tokens\n" + str(tokens.json()))
        response.set_cookie(key="jwt", value=tokens.content, httponly=True)

Updated Source Code To Demonstrate How To Resolve Code Challenge Using Authlib

import base64
from functools import lru_cache

from authlib.integrations.starlette_client import OAuth
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware

from . import config


@lru_cache()
def get_settings() -> config.Settings:
    """Create config settings instance encapsulating app config."""

    return config.Settings()


@lru_cache
def get_auth_base_url(region: str, userpool_id: str) -> str:
    """Return cognito discover points base url from region and userpool ID."""

    return ("https://cognito-idp." + region + ".amazonaws.com/" + userpool_id)


app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-string")

oauth = OAuth()
cognito = oauth.register(
    "cognito",
    client_id=get_settings().client_id,
    client_secret=get_settings().client_secret,
    server_metadata_url=get_auth_base_url(
        get_settings().region, get_settings().userpool_id
    )
    + "/.well-known/openid-configuration",
    client_kwargs={"scope": "openid email"},
)


def encode_auth_header(client_id: str, client_secret: str) -> str:
    """Encode client id and secret as base64 client_id:client_secret."""

    secret = base64.b64encode(
        bytes(client_id, "utf-8") + b":" + bytes(client_secret, "utf-8")
    )

    return "Basic " + secret.decode()


@app.get("/")
async def login(request: Request):
    """Redirect to /aws_cognito_redirect endpoint after sign-in."""

    redirect_uri = request.url_for("read_code_challenge")

    return await cognito.authorize_redirect(request, redirect_uri)


@app.get("/aws_cognito_redirect")
async def read_code_challenge(request: Request):
    """Request a token from cognito using code challenge response."""

    return await cognito.authorize_access_token(request)
0

There are 0 best solutions below