Skip to content

OAuth

OAuth lets users sign in with Google, GitHub, or a custom provider. CRUDAuth runs the authorization-code flow, links the result to a user in your database, and establishes a session on the callback.

from crudauth import CRUDAuth, SessionTransport, OAuthCredentials

auth = CRUDAuth(
    session=get_session, user_model=User, SECRET_KEY="change-me",
    redirect_base_url="https://app.example.com",
    transports=[SessionTransport()],
    oauth={
        "google": OAuthCredentials(client_id="...", client_secret="..."),
        "github": OAuthCredentials(client_id="...", client_secret="..."),
    },
)

OAuth establishes a session on the callback, so it requires a SessionTransport and a redirect_base_url. Each provider also needs a {provider}_id column on your user model (google_id, github_id, ...) to store and match the account; AuthUserMixin includes the built-in ones.

This adds two routes per provider: GET /oauth/{provider}/authorize (start the flow) and GET /oauth/{provider}/callback (finish it). The redirect URI you register with the provider is {redirect_base_url}/oauth/{provider}/callback.

The flow

The four-step OAuth authorization-code flow: your app redirects out with a state, the provider signs the user in, the callback returns a code and state which CRUDAuth rechecks, then CRUDAuth exchanges the code, links the user, and logs them in The four-step OAuth authorization-code flow: your app redirects out with a state, the provider signs the user in, the callback returns a code and state which CRUDAuth rechecks, then CRUDAuth exchanges the code, links the user, and logs them in

CRUDAuth binds the state parameter to the initiating browser via a cookie, so a stolen or forged callback can't complete someone else's login. The redirect target after login is validated against an allowlist to prevent open redirects.

Account linking

On a successful callback, CRUDAuth finds or creates the user:

  • If a user already exists with the provider's verified email, the provider account is linked to it (the {provider}_id column is set). The user can then sign in by password or by that provider.
  • Otherwise a new user is created from the provider profile. To set your own columns on that user (a required name, a default tier), use new_user_fields / new_user_defaults, which run on this path too; see Registration.

This linking logic lives in auth.oauth (an OAuthAccountService, or None when OAuth isn't configured), so a hand-written callback can reuse it: user, created = await auth.oauth.get_or_create_user(info, db). See Use the building blocks.

Custom providers

Add a provider by implementing the AbstractOAuthProvider port and registering it with OAuthProviderFactory, then pass its credentials in oauth={...} like the built-ins. See the OAuth reference for the port and factory.


Next: Sudo mode →