Skip to main content

axmp-oidc-bff-provider

FastAPI + Authlib 기반 OIDC BFF(Backend-for-Frontend) 라이브러리

Python FastAPI Authlib Lint: Ruff

소비하는 FastAPI 앱에 한 줄(install_bff)로 OIDC 로그인 / 세션 / CSRF / 토큰 자동 갱신 / 인증 리버스 프록시를 붙입니다.

요구사항: Python ≥ 3.10, FastAPI ≥ 0.115, Authlib ≥ 1.3.2


목차


주요 기능

  • BFF 패턴 — 브라우저는 불투명한 sid 쿠키만 보유하고, 토큰(access/refresh/id)·userinfo는 서버사이드 세션 스토어에 저장 → XSS로부터 토큰 보호
  • 멀티 프로바이더 — Keycloak · Okta · Google · Azure AD(Entra) · GitHub 빌트인 + 표준 OIDC IdP(예: Dream Security)를 코드 수정 없이 OIDC_BFF_PROVIDERS로 추가
  • 세션 스토어 — 인메모리(단일 인스턴스) / Redis(멀티 인스턴스·클러스터, per-sid 락 + 슬라이딩 TTL)
  • silent refresh — 액세스 토큰 만료 임박 시 자동 갱신(동시 요청 single-flight)
  • 세션 보안 — 세션 바인딩 CSRF(double-submit) · 슬라이딩 세션 + 절대 수명 상한 · sid/CSRF 쿠키 lockstep
  • BFF 리버스 프록시/api/* 를 다운스트림 API로 포워딩하며 access token을 Bearer로 주입(쿠키/CSRF/hop-by-hop 헤더 차단)
  • 순수 config 주입 — 라이브러리는 .env를 몰래 읽지 않음. 명시적 BFFConfig(...) 또는 opt-in BFFConfig.from_env()

설치

# uv
uv add axmp-oidc-bff-provider
# pip
pip install axmp-oidc-bff-provider

세션 스토어로 Redis를 쓰려면 Redis 서버가 필요합니다(아래 세션 백엔드 참고).


빠른 시작 (install_bff)

from fastapi import FastAPI
from axmp_oidc_bff_provider import install_bff, BFFConfig

config = BFFConfig.from_env()      # 환경변수/.env 에서 로드 (opt-in)
app = FastAPI()
install_bff(app, config)

install_bff(app, config) 한 번이 다음을 모두 와이어링합니다:

  • 미들웨어: SessionMiddleware + CSRFMiddleware(항상 짝) + CORSMiddleware(opt-in: cors_enabled 이고 frontend_origins 가 있을 때)
  • 라우터: /oauth2/*(로그인·콜백·로그아웃·userinfo), /api/* 프록시(upstream_api_url 설정 시에만)
  • lifespan: 세션 스토어 + 공유 httpx 클라이언트 생성/정리 (앱의 기존 lifespan을 덮지 않고 감싸서 합성)

같은 앱에 두 번 호출하면 RuntimeError(fail-fast)로 막습니다.

내 라우트 보호하기

from fastapi import Depends
from axmp_oidc_bff_provider import get_current_user, get_current_user_optional

@app.get("/me")
def me(user: dict = Depends(get_current_user)):       # 미인증 시 401
    return user

@app.get("/")
def index(user=Depends(get_current_user_optional)):   # 미인증 시 None
    return {"authenticated": user is not None}

설정 (BFFConfig)

BFFConfig는 순수 pydantic 모델입니다. 명시적 생성은 환경변수를 읽지 않습니다:

# 명시적 — env 무시
config = BFFConfig(
    default_provider="keycloak",
    keycloak_base_url="https://kc.example.com",
    keycloak_realm="myrealm",
    keycloak_client_id="my-app",
    keycloak_client_secret="...",
    session_secret_key="<랜덤 시크릿>",
    session_backend="redis",
    redis_url="redis://:password@redis:6379/0",
    frontend_origins=["https://app.example.com"],
)

# 또는 env/.env 에서 (opt-in)
config = BFFConfig.from_env()

주요 필드 — 모든 env 변수는 OIDC_BFF_ 프리픽스를 씁니다(소비 앱의 다른 설정과 충돌 방지). 즉 env 변수명 = OIDC_BFF_ + 필드명 대문자 (예: default_providerOIDC_BFF_DEFAULT_PROVIDER, keycloak_base_urlOIDC_BFF_KEYCLOAK_BASE_URL):

필드 / env 기본값 설명
default_provider keycloak /oauth2/start?provider= 없을 때 기본 IdP
providers {} 커스텀 OIDC 프로바이더 (아래 참고). env는 JSON
verify_ssl true 모든 OIDC HTTP 호출의 TLS 인증서 검증. 로컬 자체서명 IdP에서만 false
app_base_url http://localhost:8000 이 백엔드의 공개 URL — redirect_uri 조립에 사용
frontend_url http://localhost:5173 로그인/로그아웃 후 브라우저를 보낼 SPA 주소
frontend_origins ["http://localhost:5173"] CORS 허용 오리진 + 오픈리다이렉트 허용목록
cors_enabled true CORS 미들웨어 추가 여부(opt-out 가능)
csrf_exempt_paths [] 소비 앱이 CSRF 검증에서 추가로 제외할 경로 (아래 참고). env는 JSON 배열
csrf_safe_methods [] 소비 앱이 CSRF-safe로 추가할 HTTP 메서드 (아래 참고). env는 JSON 배열
session_secret_key (insecure 기본값) 세션 쿠키 서명 키. 프로덕션 필수 변경
session_cookie_name bff_session 서명 세션 쿠키 이름
session_https_only false 쿠키 Secure 플래그 (HTTPS 환경에서 true)
session_same_site lax 쿠키 SameSite
cookie_domain "" 쿠키 Domain (분리 호스트 배포용; 비우면 host-only)
session_ttl_seconds 28800 (8h) 서버사이드 세션 수명
session_sliding true 인증 요청마다 TTL 연장(절반 경과 시에만 기록)
session_absolute_max_seconds 604800 (7d) 슬라이딩과 무관한 절대 수명 상한(0=무제한)
session_backend redis memory 또는 redis
redis_url redis://localhost:6379/0 Redis 접속 URL (인증 시 redis://:pw@host:6379/0)
redis_key_prefix bff:sess: Redis 키 프리픽스
refresh_leeway_seconds 30 액세스 토큰 만료 N초 전 미리 갱신
refresh_http_timeout_seconds 8.0 refresh HTTP 타임아웃(per-sid 락 TTL 미만이어야 함)
upstream_api_url "" BFF 프록시 대상. 비우면 /api 프록시 비활성

생성 시 session_https_only=True(또는 app_base_url이 https)인데 시크릿이 insecure 기본값이면 검증 에러로 fail-fast 합니다.

세션 백엔드

session_backend로 두 가지 서버사이드 스토어 중 하나를 선택합니다.

  • memory — 프로세스 로컬 인메모리. 단일 인스턴스·로컬 개발용(재시작 시 소실, 노드 간 공유 안 됨).
  • redis (기본) — Redis 백엔드. 멀티 인스턴스·클러스터에서 세션을 공유하고 재시작에도 유지됩니다. per-sid 락으로 silent refresh를 single-flight 하고 슬라이딩 TTL을 지원합니다.

Redis 인증이 필요하면 redis_url에 자격증명을 포함하세요(예: redis://:password@host:6379/0).

CSRF 예외 경로 추가

CSRFMiddleware는 unsafe 메서드(POST/PUT/PATCH/DELETE)에 X-CSRF-Token 헤더를 요구합니다. 라이브러리는 OAuth 리다이렉트 플로우와 헬스 프로브를 빌트인 예외로 갖고 있습니다(/oauth2/start, /oauth2/callback, /oauth2/sign_out, /healthz).

소비 앱이 자기 경로를 추가로 제외하려면 csrf_exempt_paths에 넣으면 됩니다. 빌트인 예외는 항상 유지되고 여기에 더해집니다(덮어쓰지 않음).

config = BFFConfig(
    csrf_exempt_paths=[
        "/webhooks/stripe",             # 정확히 이 경로만
        "/internal/*",                  # "/internal/" 로 시작하는 서브트리 전체
        "/users/{user_id}/children",    # 경로 중간의 path parameter
    ],
)
install_bff(app, config)

env로 주입할 때는 JSON 배열입니다:

OIDC_BFF_CSRF_EXEMPT_PATHS=["/webhooks/stripe","/internal/*","/users/{user_id}/children"]

매칭 규칙 — 대상은 요청 경로 전체(ASGI scope["path"], 즉 Starlette 라우터가 매칭하는 것과 완전히 동일한 문자열):

패턴 매칭 비매칭
/webhooks/stripe /webhooks/stripe (정확히 일치) /webhooks/stripe/extra, /webhooks/github
/internal/* /internal/, /internal/tasks/run (접두어 /internal/) /internal, /internal-tools
/users/{user_id}/children /users/7/children, /users/a-b/children /users/children, /users/7/8/children, /users/7/children/extra, /users/7/parents

path parameter ({name}) — FastAPI 라우트처럼 경로 중간에 가변 세그먼트가 있을 때 씁니다.

  • {name}세그먼트 전체여야 하고, 비어 있지 않은 한 세그먼트에만 매칭됩니다(/를 넘지 않음). 이름 자체는 매칭에 쓰이지 않으니 라우트의 파라미터명과 같게 적어두면 읽기 좋습니다.
  • 여러 번 쓸 수 있고(/orgs/{org_id}/users/{user_id}), 후행 /* 와 조합할 수 있습니다(/users/{user_id}/children/*/users/7/children/ 이하 전체).
  • 리터럴 세그먼트가 최소 하나는 있어야 합니다. /{tenant}/{tenant}/* 는 사실상 catch-all 이라 /* 와 같이 거부됩니다(/{tenant}/webhooks/receive 는 OK).
  • 세그먼트 일부만 파라미터인 형태(/users/u{id})나 빈/중첩 중괄호({}, {a{b}})는 config 로드 시 거부됩니다.

앱이 프리픽스 아래에 서비스되면(root_path="/bff", 서브앱 마운트 등) 패턴에도 그 프리픽스를 포함해야 합니다(/bff/webhooks/*) — 매칭 대상이 프리픽스가 붙은 요청 경로 전체이기 때문입니다. 빠뜨리면 계속 403이 나는(=fail-closed) 형태로 드러납니다.

잘못된 설정은 config 로드 시점에 fail-fast 합니다:

  • /로 시작하지 않는 값 (webhooks/stripe)
  • 맨 끝의 /* 이외의 * (글롭 미지원). /internal* 처럼 슬래시 없는 형태도 거부합니다 — 의도치 않게 형제 경로(/internal-admin/delete)까지 덮기 때문입니다
  • 전체를 제외해 CSRF 보호를 무력화하는 /*
  • 잘못된 {name} 파라미터: 세그먼트 일부만 파라미터(/users/u{id}), 빈/중첩/짝 안 맞는 중괄호, 그리고 리터럴 세그먼트가 하나도 없는 패턴(/{tenant}/*)

검증을 우회해 설정하는 경우(model_copy(update=...), model_construct, 속성 직접 할당)에도 매처가 fail-closed 합니다: 모든 경로에 매칭될 패턴(/*, *, /{tenant}/*)은 경고 로그와 함께 무시되고, 잘못된 형태의 {name} 세그먼트는 리터럴로 취급되어 어떤 경로에도 매칭되지 않습니다.

⚠️ 예외 경로는 다른 오리진에서 사용자 쿠키를 붙여 호출할 수 있습니다. 세션을 대신해 상태를 변경하지 않는 엔드포인트, 또는 자체 인증(서명 헤더·mTLS·API 키)을 갖춘 엔드포인트만 제외하세요.

upstream_api_url이 설정된 상태에서 BFF 프록시 표면(/api/*)을 제외하면 경고 로그가 남습니다 — 프록시는 세션의 access token을 붙여 포워딩하므로, 제외 시 어떤 오리진이든 쿠키만으로 인증된 상태 변경을 유발할 수 있습니다(치명적이지 않은 경고이며, 업스트림이 자체 인증을 갖춘 경우를 위해 허용은 유지).

예외 경로에서는 미들웨어가 세션 번들을 조회하지 않습니다(빌트인 예외와 동일). 따라서 인증 의존성(get_current_user 등)을 쓰지 않는 예외 경로 요청에서는 슬라이딩 csrf_token 쿠키 재발급이 일어나지 않습니다.

CSRF safe 메서드 추가

CSRF 검증은 safe 메서드를 건너뜁니다. 라이브러리 빌트인은 상태를 바꾸지 않는 네 가지(GET, HEAD, OPTIONS, TRACE)입니다.

앱이 이 밖의 read-only 메서드를 쓴다면 — 예: 본문을 갖는 조회용 HTTP QUERY, WebDAV 계열의 PROPFIND/REPORT/SEARCHcsrf_safe_methods에 추가하면 됩니다. 빌트인은 항상 유지되고 여기에 더해집니다(덮어쓰지 않음).

config = BFFConfig(
    csrf_safe_methods=["QUERY"],   # GET/HEAD/OPTIONS/TRACE 는 그대로 유지
)
install_bff(app, config)

env로 주입할 때는 JSON 배열입니다:

OIDC_BFF_CSRF_SAFE_METHODS=["QUERY"]
  • 설정값은 공백 제거 + 대문자로 정규화됩니다(" query "QUERY).
  • 반면 들어온 요청의 메서드는 그대로 비교합니다. HTTP 메서드는 대소문자를 구분하므로 소문자 query 요청은 QUERY가 아니며, 기존과 동일하게 unsafe로 처리됩니다.
  • csrf_exempt_paths경로 단위 예외라면, 이쪽은 메서드 단위(모든 경로에 적용) 예외입니다.

거부되는 값은 오타뿐입니다. HTTP 메서드 토큰이 아닌 값("GET POST", "GE/T" — 한 항목에 하나의 메서드만)은 어떤 request.method와도 같아질 수 없으므로 config 로드 시 fail-fast 합니다.

어떤 메서드에 CSRF 보호가 필요한지는 소비 앱의 정책이므로, 위험한 값도 거부하지 않고 경고 로그와 함께 허용합니다.

config = BFFConfig(csrf_safe_methods=["QUERY", "POST"])   # 허용됨 (경고 로그)
OIDC_BFF_CSRF_SAFE_METHODS=["QUERY","POST"]
  • 상태 변경 메서드POST/PUT/PATCH/DELETE 및 WebDAV 계열 쓰기 메서드(PROPPATCH, MKCOL, COPY, MOVE, LOCK/UNLOCK, ACL, RFC 3253 버저닝, PURGE 등) — 는 강한 경고를 남깁니다. 해당 메서드의 CSRF 검사가 앱 전역에서 사라진다는 뜻이기 때문입니다. 자체 인증(서명 헤더·mTLS·API 키)이 있는 경우에만 사용하고, 특정 엔드포인트만 열고 싶다면 경로 단위 csrf_exempt_paths를 쓰세요.
  • 라이브러리가 read-only로 아는 메서드(QUERY, PROPFIND, REPORT, SEARCH)가 아닌 커스텀 메서드는 약한 경고를 남깁니다 — 쓰기 여부를 라이브러리가 알 수 없기 때문입니다.

경고는 실제로 적용되는 매처 단계에서도 한 번 더 남습니다(메서드 집합당 1회). 검증을 우회해 설정하면(model_copy(update=...), model_construct, 속성 직접 할당) config 로드 경고가 아예 뜨지 않기 때문에, 이쪽이 유일한 경고 지점입니다.

⚠️ safe 메서드는 모든 경로에서 CSRF 검사를 건너뜁니다. upstream_api_url이 설정돼 있으면 세션 access token을 붙여 포워딩하는 /api/* 프록시도 포함이므로, POST 등을 safe로 두면 세션 쿠키가 전달되는 오리진(SameSite=lax/strict면 same-site 오리진, SameSite=None이면 사실상 모든 오리진)에서 사용자 쿠키만으로 인증된 상태 변경 호출을 유발할 수 있습니다. 단, 프록시는 여전히 세션을 요구하므로(get_current_session) 로그인하지 않은 요청은 401입니다.

메서드 오버라이드 미들웨어(X-HTTP-Method-Override 등)를 CSRFMiddleware 바깥쪽에 설치하지 마세요. install_bff는 CSRF를 가장 안쪽에 넣으므로, 나중에 추가한 오버라이드 미들웨어가 먼저 실행되면 POST가 safe 메서드로 바뀐 뒤에야 CSRF가 보게 됩니다.


OIDC 프로바이더

빌트인 (5종)

각 프로바이더 자격증명을 env/BFFConfig로 설정하면 자동 등록됩니다.

모든 env 변수는 OIDC_BFF_ 프리픽스를 씁니다.

Provider 핵심 설정
keycloak OIDC_BFF_KEYCLOAK_BASE_URL, OIDC_BFF_KEYCLOAK_REALM, OIDC_BFF_KEYCLOAK_CLIENT_ID, OIDC_BFF_KEYCLOAK_CLIENT_SECRET
okta OIDC_BFF_OKTA_ORG_URL, OIDC_BFF_OKTA_CLIENT_ID, OIDC_BFF_OKTA_CLIENT_SECRET
google OIDC_BFF_GOOGLE_CLIENT_ID, OIDC_BFF_GOOGLE_CLIENT_SECRET
azure OIDC_BFF_AZURE_TENANT_ID, OIDC_BFF_AZURE_CLIENT_ID, OIDC_BFF_AZURE_CLIENT_SECRET
github OIDC_BFF_GITHUB_CLIENT_ID, OIDC_BFF_GITHUB_CLIENT_SECRET (비-OIDC OAuth2; userinfo는 api.github.com에서 조회)

Keycloak이 구버전(레거시 /auth 컨텍스트 경로)이면 OIDC_BFF_KEYCLOAK_BASE_URL/auth를 포함하세요(예: https://kc.example.com/auth).

커스텀 (표준 OIDC IdP — 코드 수정 불필요)

.well-known/openid-configuration discovery 문서를 노출하는 IdP는 OIDC_BFF_PROVIDERS(JSON)로 추가합니다:

OIDC_BFF_DEFAULT_PROVIDER=dream
OIDC_BFF_PROVIDERS={"dream": {"server_metadata_url": "https://sso.dreamsecurity.example/.well-known/openid-configuration", "client_id": "my-app", "client_secret": "...", "scope": "openid email profile", "client_kwargs": {"code_challenge_method": "S256"}}}

런타임에는 /oauth2/start?provider=dream 으로 선택합니다. 키 이름이 빌트인과 겹치면 커스텀이 우선합니다.


제공 엔드포인트

install_bff / make_auth_router/oauth2 프리픽스로 추가하는 라우트입니다. 경로·쿼리 파라미터는 AI Gateway OAuth2 plugin(oauth2-proxy 계열) 컨벤션에 맞췄습니다:

Method Path 설명
GET /oauth2/start?rd=<url>&provider=<name> 선택한 IdP로 302 리다이렉트(PKCE). provider 생략 시 기본값. rd(복귀경로)는 frontend_origins 내에서만 허용
GET /oauth2/callback 인가 코드 교환 → 서버사이드 세션(sid) 생성 → rd/frontend_url로 리다이렉트
GET /oauth2/sign_out?rd=<url> 로컬 세션 삭제 + IdP RP-initiated logout. rd(검증됨)를 post_logout_redirect_uri로 사용
GET /oauth2/userinfo 현재 사용자 OIDC userinfo 클레임 원본(JSON), 인증 필요

IdP/discovery 장애 시 /oauth2/start은 traceback 노출 없이 502로 graceful 처리됩니다.

BFF 리버스 프록시 (upstream_api_url 설정 시)

Method Path 설명
ANY /api/{path} 인증된 요청을 upstream_api_url로 포워딩, 세션의 access token을 Authorization: Bearer로 주입. 인입 Cookie·X-CSRF-Token·X-Forwarded-*·hop-by-hop 헤더 제거, 업스트림 Set-Cookie 미전달, 응답 스트리밍

SPA가 unsafe 메서드(POST 등)를 호출할 때는 X-CSRF-Token 헤더가 필요합니다(콜백이 세팅한 읽기 가능 csrf_token 쿠키 값을 echo).


고급 사용

대부분의 앱은 install_bff 한 번으로 충분합니다. 아래는 자체 lifespan·미들웨어를 함께 쓰거나, 와이어링을 직접 제어하고 싶을 때를 위한 가이드입니다.

커스텀 lifespan

소비 앱이 자체 lifespan(DB 풀, 백그라운드 작업 등)을 쓰고 싶으면, 평소처럼 FastAPI(lifespan=...)에 넘긴 뒤 install_bff를 호출하면 됩니다. install_bff가 호출 시점에 등록돼 있는 lifespan을 읽어 BFF lifespan으로 바깥에서 감쌉니다(소비자 lifespan은 그 안쪽에 중첩).

from contextlib import asynccontextmanager

from fastapi import FastAPI

from axmp_oidc_bff_provider import install_bff, BFFConfig

config = BFFConfig.from_env()


@asynccontextmanager
async def lifespan(app: FastAPI):
    # startup — BFF lifespan이 먼저 진입한 뒤라
    # app.state.bff_session_store / bff_http / bff_auth_service 를 바로 쓸 수 있음
    app.state.my_db = await create_db_pool(...)
    yield
    # shutdown
    await app.state.my_db.aclose()


app = FastAPI(lifespan=lifespan)   # 1) 내 lifespan 을 먼저 등록
install_bff(app, config)           # 2) BFF 가 그 위를 감쌈
  • 호출 순서가 중요: lifespan 은 FastAPI() 생성 시 넘기고, install_bff 는 그 뒤에 호출합니다. (install_bff 호출 후 app.router.lifespan_context 를 덮어쓰면 BFF lifespan 이 사라집니다.)
  • 실행 순서: BFF setup → 내 startup → (요청 처리) → 내 shutdown → BFF teardown. BFF 가 바깥이라 내 startup 에서 app.state.bff_* 를 바로 쓸 수 있고, 내 startup 이 실패해도 BFF teardown(세션 스토어·httpx 클라이언트 close)은 정상 수행됩니다.

커스텀 lifespan 이 없으면 install_bff 만 호출하면 됩니다 — BFF lifespan 이 단독으로 동작합니다. 미들웨어·라우터까지 직접 조립하는 경우엔 아래 고급 커스텀 설정 에서 bff_lifespan(config) 를 직접 사용하세요.

미들웨어 추가

install_bff는 내부에서 CSRF → Session → CORS 순으로 미들웨어를 깝니다(결과 스택은 바깥→안으로 CORS → Session → CSRF → routes). 여기에 내 미들웨어를 더할 때는 install_bff 앞/뒤 어디서 add_middleware를 부르냐로 위치가 정해집니다 — Starlette의 add_middleware는 항상 맨 앞에 prepend하므로 나중에 추가한 게 더 바깥입니다.

추가 시점 위치 request flow
install_bff BFF 레이어 안쪽 (CSRF보다 안, 라우트에 가깝) CORS → Session → CSRF → 내것 → routes
install_bff BFF 레이어 바깥 (CORS보다 밖) 내것 → CORS → Session → CSRF → routes
from fastapi import FastAPI
from starlette.middleware.gzip import GZipMiddleware

from axmp_oidc_bff_provider import install_bff, BFFConfig

config = BFFConfig.from_env()
app = FastAPI(lifespan=lifespan)   # (커스텀 lifespan 을 쓰면)

# (A) request.session 등 세션/인증 컨텍스트가 필요한 미들웨어 → install_bff "전"
#     → SessionMiddleware 안쪽에 위치해 request.session 사용 가능
app.add_middleware(MyTenantContextMiddleware)

install_bff(app, config)

# (B) 전체를 감싸는 cross-cutting(요청ID·로깅·타이밍·GZip·보안헤더) → install_bff "후"
app.add_middleware(GZipMiddleware, minimum_size=1024)
app.add_middleware(RequestIDMiddleware)   # 나중 호출이 더 바깥
# 결과: RequestID → GZip → CORS → Session → CSRF → MyTenantContext → routes
  • request.session을 읽는 미들웨어는 반드시 install_bff 전에 추가하세요. 바깥(후)에 두면 SessionMiddleware가 아직 안 돌아 request.session 접근이 AssertionError로 터집니다.
  • CSRF보다 안쪽 = CSRF를 통과한 요청만 봅니다(unsafe 메서드 기준). CSRF 거부까지 포함해 로깅하려면 install_bff 후(바깥)에 두세요.
  • CORS는 가장 바깥이 정석(프리플라이트 우선 응답). install_bff 후에 무거운/조기 응답 미들웨어를 두면 CORS 밖이 되니 유의하세요(로깅·요청ID 정도는 무방).
  • 둘 다 앱 시작 전에 호출해야 합니다 — 시작 후나 lifespan 안에서 add_middleware를 부르면 RuntimeError(Cannot add middleware after an application has started)가 납니다.
  • 생성자 FastAPI(lifespan=..., middleware=[Middleware(X, ...)])로 넘긴 것도 "install_bff 전 추가"와 동일하게 innermost가 됩니다.

순서를 상대적 위치에 의존하지 않고 완전히 명시적으로 짜려면 아래 고급 커스텀 설정 처럼 install_bff 없이 미들웨어를 직접 원하는 순서로 add_middleware 하세요.

고급 커스텀 설정

install_bff가 내부적으로 쓰는 팩토리를 직접 조립할 수도 있습니다. 미들웨어 순서·소유권을 완전히 제어하고 싶을 때 사용하세요.

from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
from axmp_oidc_bff_provider import (
    BFFConfig, build_oauth, make_auth_router, make_proxy_router,
    CSRFMiddleware, bff_lifespan,
)

config = BFFConfig.from_env()
app = FastAPI(lifespan=bff_lifespan(config))   # 세션 스토어 + httpx 클라이언트 관리

# 의존성/라우터가 런타임에 읽는 값
app.state.bff_config = config
app.state.bff_oauth = build_oauth(config)

# add_middleware는 나중에 추가한 게 바깥 → CSRF 먼저, Session 다음 (Session이 바깥)
app.add_middleware(CSRFMiddleware, config=config)
app.add_middleware(
    SessionMiddleware,
    secret_key=config.session_secret_key,
    session_cookie=config.session_cookie_name,
    same_site=config.session_same_site,
    https_only=config.session_https_only,
    max_age=config.session_ttl_seconds,
)

app.include_router(make_auth_router(config, app.state.bff_oauth))
proxy = make_proxy_router(config)              # upstream 미설정 시 None
if proxy is not None:
    app.include_router(proxy)

SessionMiddlewareCSRFMiddleware는 한 쌍입니다(CSRF가 request.session을 읽음). 둘은 항상 함께, Session이 바깥에 오도록 추가하세요.


공개 API

from axmp_oidc_bff_provider import (
    # 와이어링
    install_bff, bff_lifespan,
    # 설정
    BFFConfig, OIDCProviderConfig, INSECURE_DEFAULT_SECRET,
    # 팩토리 (고급 커스텀 설정용)
    build_oauth, make_auth_router, make_proxy_router, CSRFMiddleware,
    # CSRF 예외 경로 (빌트인 집합 + 매처)
    EXEMPT_PATHS, is_csrf_exempt,
    # 세션 스토어
    create_session_store, SessionStore, InMemorySessionStore, RedisSessionStore,
    # 의존성
    get_current_user, get_current_user_optional, get_current_session,
    # 유틸
    SID_KEY, hash_sid,
)

패키지를 import 해도 부작용이 없습니다(env 읽기·OAuth 등록 없음). 심볼은 최초 접근 시 지연 로드됩니다(PEP 562).


데모 앱 실행

examples/main.pyinstall_bff + 데모 라우트(/, /protected, /healthz)를 보여줍니다.

# 1) 의존성 + 로컬 스택(Keycloak + Redis)
uv sync
docker compose up -d

# 2) 환경변수
cp .env.example .env   # OIDC_BFF_KEYCLOAK_*, OIDC_BFF_SESSION_SECRET_KEY 등 채우기

# 3) 서버 (examples/ 가 pythonpath)
uv run uvicorn main:app --app-dir examples --port 8000 --reload

OIDC_BFF_SESSION_SECRET_KEYpython -c "import secrets; print(secrets.token_urlsafe(48))"로 생성하세요.


프로덕션 노트

  • HTTPS: OIDC_BFF_SESSION_HTTPS_ONLY=true (쿠키 Secure). http 환경에서 true면 콜백 state 쿠키가 왕복되지 않아 로그인이 깨집니다.
  • Redis 필수(멀티 인스턴스): OIDC_BFF_SESSION_BACKEND=redis로 모든 인스턴스가 세션을 공유. 인증이 필요한 Redis는 OIDC_BFF_REDIS_URL=redis://:<password>@host:6379/0.
  • 시크릿 관리: OIDC_BFF_SESSION_SECRET_KEY, 모든 OIDC_BFF_*_CLIENT_SECRET을 시크릿 매니저(Vault/K8s Secret 등)로 주입.
  • 분리 호스트 SPA: SPA와 BFF가 다른 호스트면 OIDC_BFF_COOKIE_DOMAIN을 설정하고 OIDC_BFF_FRONTEND_ORIGINS에 SPA 오리진을 추가(CORS credentialed).
  • /healthz: Redis 백엔드일 때 ping하여 다운 시 503(degraded) 반환.

개발과 테스트

uv sync
uv run ruff check src/ examples/ tests/
uv run pytest -q

테스트는 라이브러리 컴포넌트(install_bff + 주입 BFFConfig)로 앱을 빌드하며, 데모 앱에 의존하지 않습니다.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

axmp_oidc_bff_provider-0.12.0.tar.gz (190.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

axmp_oidc_bff_provider-0.12.0-py3-none-any.whl (71.4 kB view details)

Uploaded Python 3

File details

Details for the file axmp_oidc_bff_provider-0.12.0.tar.gz.

File metadata

  • Download URL: axmp_oidc_bff_provider-0.12.0.tar.gz
  • Upload date:
  • Size: 190.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for axmp_oidc_bff_provider-0.12.0.tar.gz
Algorithm Hash digest
SHA256 57e7580657e9dd0dc90726f00dc83e445b7fd2b8fe2e6d7a999b8dad7cb64c5a
MD5 1b5807e282ad6d1074d6dc0030aebfcc
BLAKE2b-256 9972894d472ad5baf878d588775f0f460f28f270a8ece98ef043ddee39b2b7a7

See more details on using hashes here.

File details

Details for the file axmp_oidc_bff_provider-0.12.0-py3-none-any.whl.

File metadata

File hashes

Hashes for axmp_oidc_bff_provider-0.12.0-py3-none-any.whl
Algorithm Hash digest
SHA256 269939f178e8634d03091d1a390c0b9cfc7543f9557420fd0972ef3865adbdb1
MD5 4bf568a55edfd853400d590d702228ec
BLAKE2b-256 25688de782052347e3eb2e84ff6d88f494bb82991c0770e3eca8b92b76bec13e

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.12.0 This release

2 files

0.11.0

2 files

0.10.0

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page