Sigra.OAuth (Sigra v1.20.0)

Copy Markdown View Source

OAuth orchestrator for Sigra authentication.

Coordinates the full OAuth flow: authorization URL generation with HMAC-signed state, callback processing with account routing (register/login/link-confirm), token refresh, and link/unlink operations.

Architecture (D-20)

  • Sigra.OAuth -- orchestrator (this module)
  • Sigra.OAuth.Callback -- response processing and account routing
  • Sigra.OAuth.Strategies.* -- per-provider Assent wrappers

HMAC State (D-16)

Sigra owns the OAuth CSRF state parameter. On authorize, a nonce is generated and signed via Sigra.Token.generate/4 with purpose "sigra-oauth-state" and a 15-minute TTL. The callback verifies this signature before processing the token exchange.

Account Scenarios

The callback processor handles five scenarios:

  1. Existing identity -- user logged in, identity fields updated (D-31)
  2. New user -- registered with auto-confirmed email (D-42), remember-me session (D-43)
  3. Email match -- existing user with same email, link confirmation required (D-01)
  4. No email -- provider didn't return email, error shown (D-08)
  5. UID/email conflict -- identity maps to user A, email maps to user B, blocked (D-09)

Summary

Functions

Generates an authorization URL for the given provider.

Retrieves OAuth tokens for an identity, auto-refreshing if expired.

Handles an OAuth callback from the provider.

Links an OAuth provider to an existing authenticated user.

Unlinks an OAuth provider from a user.

Functions

authorize_url(config, provider, opts \\ [])

(since 0.5.0)
@spec authorize_url(map(), atom(), keyword()) ::
  {:ok, String.t(), map()}
  | {:error,
     atom()
     | %Sigra.Error.OAuthError{
         __exception__: true,
         error_code: term(),
         message: term(),
         provider: term()
       }}

Generates an authorization URL for the given provider.

Resolves the provider's strategy wrapper, calls its authorize_url/1, and replaces the state parameter with an HMAC-signed version.

Returns {:ok, url, session_params} where session_params includes :sigra_state and any PKCE :code_verifier.

Examples

{:ok, url, session_params} = Sigra.OAuth.authorize_url(config, :google)

get_tokens(config, identity)

(since 0.5.0)
@spec get_tokens(map(), Sigra.Identity.t()) :: {:ok, map()} | {:error, :token_expired}

Retrieves OAuth tokens for an identity, auto-refreshing if expired.

When the access token is expired and a refresh token exists, calls the provider's token refresh endpoint, persists the new tokens, and returns updated tokens. If no refresh token is available or refresh fails, returns {:error, :token_expired}.

The returned map uses logical key names (:access_token, :refresh_token). The identity struct fields are named encrypted_* because the generated Ecto schema uses Cloak for transparent encryption -- when loaded through Ecto, these fields contain plaintext (decrypted) values.

Examples

{:ok, %{access_token: "plaintext_token"}} = Sigra.OAuth.get_tokens(config, identity)

handle_callback(config, provider, params, session_params)

(since 0.5.0)
@spec handle_callback(map(), atom(), map(), map()) ::
  {:ok, atom(), map(), map()}
  | {:link_confirmation_required, map()}
  | {:error,
     %Sigra.Error.OAuthError{
       __exception__: true,
       error_code: term(),
       message: term(),
       provider: term()
     }}

Handles an OAuth callback from the provider.

Verifies the HMAC-signed state parameter, calls the strategy's callback/3 to exchange the authorization code for tokens and user info, then delegates to Sigra.OAuth.Callback.process_callback/4 for account routing.

Returns

  • {:ok, :registered, user, session} -- new user created
  • {:ok, :logged_in, user, session} -- existing identity matched
  • {:link_confirmation_required, %{provider: p, email: e, ...}} -- email match
  • {:error, %OAuthError{}} -- state mismatch, no email, provider error, etc.