Skip to main content

Authentication

Fastman provides one-command authentication scaffolding for the most common auth patterns. Each generates production-ready code with proper security practices built in.

TypeBest ForCommand
JWTStateless APIs, mobile appsfastman install:auth --type=jwt
OAuthSocial login (Google, GitHub, Discord)fastman install:auth --type=oauth --provider=google
KeycloakEnterprise SSOfastman install:auth --type=keycloak
PasskeyPasswordless (biometric/hardware key)fastman install:auth --type=passkey

JWT Authentication

The most common choice for stateless API authentication. Tokens are signed, self-contained, and require no server-side session storage.

fastman install:auth --type=jwt

This creates a complete auth feature module:

app/features/auth/
├── __init__.py
├── models.py # User model with password hashing
├── schemas.py # Login, Register, Token schemas
├── security.py # JWT token creation/verification
├── service.py # Auth business logic
├── dependencies.py # get_current_user dependency
└── router.py # /register, /login, /me endpoints

Generated Endpoints

MethodEndpointDescription
POST/auth/registerCreate new user
POST/auth/loginGet access token
GET/auth/meGet current user

Usage Example

from app.features.auth.dependencies import get_current_user
from app.features.auth.models import User

@router.get("/protected")
def protected_route(user: User = Depends(get_current_user)):
return {"message": f"Hello, {user.email}"}

Configuration

Auth variables are automatically added to all environment files (.env.*):

SECRET_KEY=your-secret-key-here
ACCESS_TOKEN_EXPIRE_MINUTES=30

Generate a secure secret key (updates all env files):

fastman config:appkey

OAuth Authentication

For "Login with Google/GitHub/etc." social login flows. Fully scaffolded with provider-specific configuration.

# Google (default)
fastman install:auth --type=oauth --provider=google

# GitHub
fastman install:auth --type=oauth --provider=github

# Discord
fastman install:auth --type=oauth --provider=discord

Supported Providers

ProviderScopesWhat You Get
Googleopenid email profileEmail, name, avatar
GitHubread:user user:emailEmail, name, avatar
Discordidentify emailEmail, username, avatar

Generated Files

app/features/auth/
├── oauth_config.py # Provider configuration (authlib)
├── models.py # User model with oauth_provider field
├── schemas.py # UserRead, OAuthCallback schemas
├── service.py # Create-or-update user from OAuth data
└── router.py # /login, /callback, /me, /logout

Generated Endpoints

MethodEndpointDescription
GET/auth/loginRedirect to OAuth provider
GET/auth/callbackHandle provider callback
GET/auth/meGet current user
GET/auth/logoutClear session

Configuration

OAUTH_CLIENT_ID=your-client-id
OAUTH_CLIENT_SECRET=your-client-secret
tip

Add SessionMiddleware to your app/main.py for OAuth session support:

from starlette.middleware.sessions import SessionMiddleware
app.add_middleware(SessionMiddleware, secret_key=settings.SECRET_KEY)

Keycloak Authentication

Enterprise-grade identity and access management with single sign-on (SSO). Powered by fastapi-keycloak.

fastman install:auth --type=keycloak

If your Keycloak instance or upstream gateway uses a private CA, append your project certificates during setup:

fastman install:auth --type=keycloak --append-certificate

This:

  1. Installs fastapi-keycloak and certifi
  2. Creates app/core/keycloak.py with lazy-initialized FastAPIKeycloak instance, Swagger OAuth config, and a GET /me endpoint
  3. Updates app/core/config.py with Keycloak settings
  4. Adds environment variables to all .env.* files
  5. Automatically uses certificates from certs/ when present by building a merged CA bundle for the running app
Lazy initialization and startup fallback

The FastAPIKeycloak instance is not created at import time. It is initialized inside init_keycloak(app) during application startup. This gives you a safer startup flow:

  • Custom certificates are loaded before any HTTPS call to Keycloak
  • No network calls happen during module import
  • idp and get_current_user are populated only after init_keycloak() runs
  • If Keycloak is temporarily unreachable, the app still starts and logs that Keycloak is disabled
  • If Keycloak returns unauthorized_client, the app still starts with admin features disabled

Generated Endpoint

MethodEndpointDescription
GET/meReturn the current authenticated Keycloak user

Configuration

KEYCLOAK_URL=https://keycloak.example.com
KEYCLOAK_REALM=my-realm
KEYCLOAK_CLIENT_ID=my-client
KEYCLOAK_CLIENT_SECRET=your-secret
KEYCLOAK_ADMIN_SECRET=
KEYCLOAK_CALLBACK_URI=http://localhost:8000/callback
KEYCLOAK_VERIFY_SSL=true
VariableDescriptionDefault
KEYCLOAK_URLKeycloak server URL (with /auth suffix if required by your version)http://localhost:8080
KEYCLOAK_REALMKeycloak realm namemaster
KEYCLOAK_CLIENT_IDClient ID for your application
KEYCLOAK_CLIENT_SECRETClient secret
KEYCLOAK_ADMIN_SECRETSecret for the admin-cli client. Only needed if you want admin operations such as managing users, roles, or groups from code.empty
KEYCLOAK_CALLBACK_URIOAuth2 callback URL — must match a Valid Redirect URI in Keycloakhttp://localhost:8000/callback
KEYCLOAK_VERIFY_SSLtrue — enable SSL verification; false — disable (not recommended for production)true

Minimal vs Admin-Enabled Setup

For most projects, you only need these values:

  • KEYCLOAK_URL
  • KEYCLOAK_REALM
  • KEYCLOAK_CLIENT_ID
  • KEYCLOAK_CLIENT_SECRET
  • KEYCLOAK_CALLBACK_URI

That is enough for:

  • Depends(get_current_user) route protection
  • the generated GET /me endpoint
  • Swagger's Authorize button

KEYCLOAK_ADMIN_SECRET is only needed when you want to call admin methods on idp, such as creating users or assigning roles. Fastman does not expose a KEYCLOAK_ADMIN_CLIENT_ID setting; fastapi-keycloak uses its default admin client (admin-cli) internally.

Protecting Routes

Unlike middleware-based approaches, fastapi-keycloak uses dependency injection. Routes are unprotected by default — you opt in per-route:

from fastapi import Depends
from fastapi_keycloak import OIDCUser
from app.core.keycloak import get_current_user

@router.get("/protected")
def protected_route(user: OIDCUser = Depends(get_current_user)):
return {"message": f"Hello, {user.email}"}

To require specific roles:

from app.core.keycloak import idp

get_admin = idp.get_current_user(required_roles=["admin"])

@router.get("/admin")
def admin_route(user: OIDCUser = Depends(get_admin)):
return {"message": f"Welcome admin {user.email}"}

Swagger Authorize Button

init_keycloak(app) calls idp.add_swagger_config(app) and registers the /me endpoint. The Swagger UI automatically gets an Authorize button — click it to authenticate with Keycloak before testing protected endpoints.

If Keycloak cannot be reached at startup, the app still boots and your non-Keycloak routes continue to work. In that state, /me and any route protected with get_current_user remain unavailable until Keycloak becomes reachable again.

Private CA / Certificate Support

For environments that terminate TLS with an internal CA (e.g. corporate proxies, on-prem Keycloak), store your certificate chain in the project-level certs/ directory using .pem or .crt files.

your-project/
├── app/
├── certs/
│ └── company-root-chain.pem
└── pyproject.toml

Certificates are loaded automatically at startup. When init_keycloak(app) runs, it scans certs/ for .pem and .crt files, builds a merged CA bundle from certifi plus your project certificates, and points requests and Python SSL at that merged bundle before creating the FastAPIKeycloak instance. No separate script or manual step is needed — just drop your certs and start the server.

You can also set CERTS_PATH environment variable to point to a custom certificates directory instead of certs/.

Alternatively, you can prepare the certificate bundle independently:

# Prepare during Keycloak installation
fastman install:auth --type=keycloak --append-certificate

# Or as a standalone step
fastman install:cert

This approach is non-destructive: the installed certifi bundle is left untouched, and the generated merged bundle is rebuilt when the app starts. fastman install:cert is optional for the generated Keycloak scaffold, but useful when you want to prebuild the bundle, validate your certificate files, or expose the paths through env files for other tools and scripts.


Passkey Authentication (WebAuthn)

Passwordless authentication using biometrics, hardware keys, or device PINs. No passwords, no MFA — just tap your fingerprint or security key.

fastman install:auth --type=passkey

How It Works

  1. Registration: User's device creates a public/private key pair. The public key is stored on your server.
  2. Authentication: The server sends a challenge, the device signs it with the private key, and the server verifies using the stored public key.

No password is ever created, stored, or transmitted.

Generated Files

app/features/auth/
├── models.py # User + PasskeyCredential models
├── schemas.py # Registration/Authentication request schemas
├── service.py # WebAuthn challenge generation & verification
├── security.py # JWT session tokens (post-auth)
├── dependencies.py # get_current_user dependency
└── router.py # Registration & authentication endpoints

Generated Endpoints

MethodEndpointDescription
POST/auth/passkey/register/optionsGet registration challenge
POST/auth/passkey/register/verifyStore new passkey
POST/auth/passkey/authenticate/optionsGet login challenge
POST/auth/passkey/authenticate/verifyVerify & get token
GET/auth/passkey/meCurrent user info
GET/auth/passkey/credentialsList registered passkeys
DELETE/auth/passkey/credentials/{id}Remove a passkey

Configuration

PASSKEY_RP_ID=localhost
PASSKEY_RP_NAME=My App
PASSKEY_ORIGIN=http://localhost:8000

In production, set PASSKEY_RP_ID to your domain (e.g. example.com) and PASSKEY_ORIGIN to https://example.com.

Frontend Integration

The backend provides the WebAuthn options and verification. You'll need a small JavaScript client to interact with the browser's navigator.credentials API:

// Registration
const options = await fetch('/auth/passkey/register/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', username: 'user' })
}).then(r => r.json());

const credential = await navigator.credentials.create({ publicKey: options });

await fetch('/auth/passkey/register/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com', credential })
});
Why Passkeys?

Passkeys eliminate passwords entirely. No password reuse, no phishing, no MFA fatigue. A single biometric scan or hardware tap replaces passwords + SMS codes + authenticator apps.


Best Practices

  1. Always use HTTPS in production
  2. Store secrets in environment variables, never in code
  3. Use short-lived tokens (15-30 minutes for JWT, 60 minutes for passkey sessions)
  4. Implement refresh tokens for long sessions
  5. Hash passwords with bcrypt (Fastman does this automatically for JWT)
  6. Prefer passkeys for new projects — they're more secure and easier for users