Get the Hanko API URL

Retrieve the API URL from the Hanko console.

If you are self-hosting Hanko you need to provide your own URL.

Hanko Authentication with JWT

Upon a successful login, Hanko sends a cookie containing a JSON Web Token (JWT). You can use this JWT to authenticate requests on your backend.

Steps to Authenticate Requests

  1. Retrieve the JSON Web Key Set (JWKS): The JWKS has the public keys to verify the JWT. Fetch it from the Hanko API’s .well-known/jwks.json endpoint.

  2. Verify the JWT: Use the JWKS to verify the JWT.

Python-based Backend Examples

Below we show examples of using a custom middleware in FastAPI, Django and Flask, based backend using the PyJWT package.

Since you will be decoding the token using the RSA digital signature algorithm, you will need to install the cryptography library. You can install this library either directly or as an additional requirement within the PyJWT package.

pip install pyjwt[crypto]

The pyjwt[crypto] format is recommended in requirements files in projects using PyJWT, as a separate cryptography requirement line may later be mistaken for an unused requirement and removed.

from typing import Any
import os
import ssl
import jwt
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

HANKO_API_URL = os.environ.get("HANKO_API_URL")


def deny():
    return JSONResponse(content={"error": "Unauthorized"}, status_code=401)


def extract_token_from_header(header: str) -> str:
    parts = header.split()
    return parts[1] if len(parts) == 2 and parts[0].lower() == "bearer" else None


app = FastAPI()


@app.middleware("http")
async def auth(request: Request, call_next: Any):
    authorization = request.headers.get("authorization")

    if not authorization:
        return deny()

    token = extract_token_from_header(authorization)

    if not token:
        return deny()

    try:
         # Disable SSL certificate verification while in development. 
         # Don't forget to remove this when in prod.

        ssl_context = ssl.create_default_context()
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE

        jwks_client = jwt.PyJWKClient(
            HANKO_API_URL + "/.well-known/jwks.json", ssl_context=ssl_context
        )
        signing_key = jwks_client.get_signing_key_from_jwt(token)
        data = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            audience="localhost",
        )

        if not data:
            return deny()

        return await call_next(request)

    except (jwt.DecodeError, Exception) as e:
        print(f"Authentication error: {e}")
        return deny()


@app.get("/")
async def root():
    return {"message": "Hello World"}


@app.get("/protected")
async def protected():
    return {"message": "Hello World"}

While using PyJWT to decode JWTs, you might encounter errors such as binascii.Error: Incorrect padding and jwt.exceptions.DecodeError: Invalid crypto padding. These errors often occur due to insufficient base64 padding at the end of the token.

To resolve this, you might need to manually add the necessary padding to the token before attempting to decode it. You can read the token as a string and append the required padding characters to ensure successful decoding.

token = token + "===="