From 3a218edfa26f7d1397fded06c4ecd9810b338ef8 Mon Sep 17 00:00:00 2001 From: Johnny Bouder Date: Fri, 13 Oct 2023 13:52:33 -0400 Subject: [PATCH] Add bearer token endpoint example to project. Updated configs and readme for oidc provider. --- README.md | 1 + app/admin/__init__.py | 0 app/admin/router.py | 21 +++++++++++++++++++++ app/auth.py | 40 ++++++++++++++++++++++++++++++++++++++++ app/config.py | 1 + app/main.py | 2 ++ requirements.txt | 2 ++ 7 files changed, 67 insertions(+) create mode 100644 app/admin/__init__.py create mode 100644 app/admin/router.py create mode 100644 app/auth.py diff --git a/README.md b/README.md index 153ebd6..a1e55c4 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ pip install -r requirements.txt ``` DATABASE_URL=[SOME_URL] # Ex: 'sqlite:///./db.sqlite3' +OIDC_CONFIG_URL=[SOME_URL] # Ex: 'https://token.actions.githubusercontent.com/.well-known/openid-configuration' ``` 4. To start the app, run the following: diff --git a/app/admin/__init__.py b/app/admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/admin/router.py b/app/admin/router.py new file mode 100644 index 0000000..808b4a0 --- /dev/null +++ b/app/admin/router.py @@ -0,0 +1,21 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from fastapi.security import HTTPBearer +from starlette import status + +from app.auth import validate_jwt + +router = APIRouter( + prefix="/api", + tags=["Admin"], +) + + +@router.get( + "/current-user", + dependencies=[Depends(HTTPBearer())], + status_code=status.HTTP_200_OK, +) +async def get_current_user(current_user: Annotated[dict, Depends(validate_jwt)]): + return {"user": current_user} diff --git a/app/auth.py b/app/auth.py new file mode 100644 index 0000000..55669af --- /dev/null +++ b/app/auth.py @@ -0,0 +1,40 @@ +import requests +from fastapi import Depends, HTTPException +from jose.backends import RSAKey +from jose.jwt import decode, get_unverified_header + +from app.config import settings + + +def get_keycloak_jwks(): + keycloak_well_known_url = settings.OIDC_CONFIG_URL + response = requests.get(keycloak_well_known_url) + well_known_config = response.json() + jwks_url = well_known_config["jwks_uri"] + jwks_response = requests.get(jwks_url) + jwks = jwks_response.json() + return jwks["keys"] + + +def validate_jwt(token: str = Depends(get_keycloak_jwks)): + header = get_unverified_header(token) + jwks = get_keycloak_jwks() + + # Find the RSA key with the matching kid in the JWKS + rsa_key = None + for key in jwks: + if key["kid"] == header["kid"]: + rsa_key = RSAKey( + key=key["n"], alg=key["alg"], use=key["use"], kid=key["kid"] + ) + break + + if rsa_key is None: + raise HTTPException(status_code=401, detail="RSA Key not found in JWKS") + + try: + payload = decode(token, rsa_key, algorithms=[header["alg"]]) + except Exception: + raise HTTPException(status_code=401, detail="Invalid JWT token") from Exception + + return payload diff --git a/app/config.py b/app/config.py index 370b14c..ca1ef11 100644 --- a/app/config.py +++ b/app/config.py @@ -3,6 +3,7 @@ class Settings(BaseSettings): DATABASE_URL: str + OIDC_CONFIG_URL: str class Config: env_file = ".env" diff --git a/app/main.py b/app/main.py index ada7fef..82e9aaf 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI +from app.admin.router import router as admin_router from app.db import Base, engine from app.health.router import router as health_router from app.spacecraft.router import router as spacecraft_router @@ -11,3 +12,4 @@ # Add routes app.include_router(health_router) app.include_router(spacecraft_router) +app.include_router(admin_router) diff --git a/requirements.txt b/requirements.txt index d4c3a71..4ebe73f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,8 @@ pydantic-settings pysqlite3 pytest python-dotenv +python-jose[cryptography] +requests ruff sqlalchemy uvicorn[standard]