Skip to content

OAuth Overview

AuthFort supports OAuth 2.1 with PKCE. Users can sign in with their existing accounts, and AuthFort handles the full flow — authorization URL, code exchange, user info fetching, and account linking.

  1. User clicks “Sign in with Google” in your frontend
  2. Your app redirects to /auth/oauth/google/authorize
  3. AuthFort redirects to Google’s consent screen
  4. Google redirects back to /auth/oauth/google/callback with an auth code
  5. AuthFort exchanges the code for user info and creates/links the account
  6. User is redirected to your app with tokens set
from authfort import AuthFort, CookieConfig, GoogleProvider, GitHubProvider

auth = AuthFort(
    database_url="postgresql+asyncpg://user:pass@localhost/mydb",
    cookie=CookieConfig(),
    providers=[
        GoogleProvider(client_id="...", client_secret="..."),
        GitHubProvider(client_id="...", client_secret="..."),
    ],
)
from authfort import AuthFort, CookieConfig, GoogleProvider, GitHubProvider

auth = AuthFort(
    database_url="postgresql+asyncpg://user:pass@localhost/mydb",
    cookie=CookieConfig(),
    providers=[
        GoogleProvider(client_id="...", client_secret="..."),
        GitHubProvider(client_id="...", client_secret="..."),
    ],
)

Providers have REQUIRED_SCOPES that are always included (e.g., openid, email, profile for Google). Use extra_scopes to request additional permissions:

from authfort import GoogleProvider

# Request additional Google API scopes
provider = GoogleProvider(
    client_id="...",
    client_secret="...",
    extra_scopes=("https://www.googleapis.com/auth/calendar",),
)
# REQUIRED_SCOPES ("openid", "email", "profile") are always included automatically
from authfort import GoogleProvider

# Request additional Google API scopes
provider = GoogleProvider(
    client_id="...",
    client_secret="...",
    extra_scopes=("https://www.googleapis.com/auth/calendar",),
)
# REQUIRED_SCOPES ("openid", "email", "profile") are always included automatically

See the individual provider pages for setup instructions:

MethodEndpointDescription
GET/auth/oauth/{provider}/authorizeStart OAuth flow (redirects to provider)
GET/auth/oauth/{provider}/callbackHandle provider callback

Replace {provider} with google, github, or any generic provider name (e.g., gitlab, keycloak).

The state, PKCE code challenge, and redirect URI are all handled internally by the server — the client just hits the authorize URL and gets redirected.

ParamDescription
redirect_toRelative path to redirect to after auth (e.g., /dashboard). Must start with /.
modeSet to popup to return HTML with postMessage instead of a redirect.
# Authorize URL with redirect_to
GET /auth/oauth/google/authorize?redirect_to=/dashboard

# After successful auth, user is redirected to /dashboard instead of
# the default callback response. Must be a relative path (starts with /).
# Authorize URL with redirect_to
GET /auth/oauth/google/authorize?redirect_to=/dashboard

# After successful auth, user is redirected to /dashboard instead of
# the default callback response. Must be a relative path (starts with /).

If your frontend and API are on different domains (e.g., app.example.com and api.example.com), set frontend_url so that redirect_to paths land on the correct domain:

auth = AuthFort(
database_url="...",
cookie=CookieConfig(domain=".example.com"),
frontend_url="https://app.example.com",
providers=[GoogleProvider(client_id="...", client_secret="...")],
)

Without frontend_url, a redirect_to=/dashboard redirects to api.example.com/dashboard. With it, the redirect goes to https://app.example.com/dashboard.

Popup mode works cross-origin without frontend_url since it uses postMessage instead of HTTP redirects.

When a user signs in with OAuth, AuthFort checks if the provider email matches an existing user:

  • Email match found — the OAuth account is linked to the existing user. An oauth_link event is emitted.
  • No match — a new user is created with the provider’s email, name, and avatar. A user_created event is emitted.

This means a user who signed up with email/password can later sign in with Google (if the emails match) without creating a duplicate account.

AuthFort stores the OAuth access and refresh tokens from the provider. You can retrieve them to call the provider’s API on behalf of the user:

# Get stored OAuth tokens for a user
tokens = await auth.get_provider_tokens(user_id, "google")
if tokens:
    print(tokens["access_token"])
    print(tokens["refresh_token"])
    print(tokens["expires_at"])
# Get stored OAuth tokens for a user
tokens = await auth.get_provider_tokens(user_id, "google")
if tokens:
    print(tokens["access_token"])
    print(tokens["refresh_token"])
    print(tokens["expires_at"])

Returns None if the user has no linked account for that provider.

In popup mode, the callback returns an HTML page that posts the auth result to the opener window via postMessage, then closes. This is useful for SPAs that don’t want a full-page redirect:

// Popup mode — opens a window, returns a promise
const user = await auth.signInWithProvider('google', { mode: 'popup' });
console.log(user.email);
// Popup mode — opens a window, returns a promise
const user = await auth.signInWithProvider('google', { mode: 'popup' });
console.log(user.email);

The client SDK handles the popup window lifecycle and message handling automatically.

OAuth emits user_created, login, login_failed, and oauth_link events. See Events & Hooks for all events and their payloads.

In the browser, use signInWithProvider() to start the OAuth flow:

// Redirect mode (default)
auth.signInWithProvider('google');

// With redirect destination
auth.signInWithProvider('google', { redirectTo: '/dashboard' });

// Popup mode
const user = await auth.signInWithProvider('google', { mode: 'popup' });
// Redirect mode (default)
auth.signInWithProvider('google');

// With redirect destination
auth.signInWithProvider('google', { redirectTo: '/dashboard' });

// Popup mode
const user = await auth.signInWithProvider('google', { mode: 'popup' });

After the callback, the user is redirected to your app with tokens set. The client SDK picks up the auth state automatically via initialize(). Framework integrations (React, Vue, Svelte) call initialize() automatically.