howdy/auth/provider

External identity providers. Install one with auth.with_provider.

The built-in constructors in howdy/auth/providers cover Google, GitHub, Facebook, Microsoft Entra, Apple and any OpenID Connect issuer (providers/oidc). For anything else, custom takes the two steps of an OAuth 2.0 authorization-code flow and leaves everything around them (state, PKCE, nonce, cookies, linking, MFA and sessions) to Howdy.

Types

What to put in the provider’s authorization URL. Send redirect_uri and state unchanged. challenge is a PKCE S256 code challenge: send it as code_challenge with code_challenge_method=S256 if the provider supports PKCE. nonce is for OpenID Connect providers; others ignore it.

pub type Authorization {
  Authorization(
    redirect_uri: String,
    state: String,
    nonce: String,
    challenge: String,
  )
}

Constructors

  • Authorization(
      redirect_uri: String,
      state: String,
      nonce: String,
      challenge: String,
    )

A returning callback, with state already checked. Redeem code at the provider’s token endpoint with this redirect_uri and, if you sent a challenge, verifier as code_verifier. An OpenID Connect ID token’s nonce must pass nonce_matches.

pub type Exchange {
  Exchange(
    code: secret.Secret,
    redirect_uri: String,
    verifier: secret.Secret,
    nonce_digest: String,
  )
}

Constructors

pub opaque type Provider

Who the provider says signed in, returned by a custom provider’s verify.

subject is the provider’s stable, unique identifier for the account, never an email address or anything else its owner can change: accounts are linked by it. At most 255 bytes.

email is the address the provider reports, or empty. Howdy never attaches a provider sign-in to an existing account because the addresses match; the user links it from the account page. The address only matters when the provider sign-in creates a new account, which needs email_authoritative: set it only when the provider is the authority for that address, as Google is for @gmail.com, not merely when it once verified the address. Setting it wrongly lets anyone who can make an account at the provider claim an address that is not theirs. When unsure, leave it False: new users then register by email and link.

pub type Verified {
  Verified(
    subject: String,
    email: String,
    email_authoritative: Bool,
  )
}

Constructors

  • Verified(
      subject: String,
      email: String,
      email_authoritative: Bool,
    )

Values

pub fn custom(
  id id: String,
  name name: String,
  issuer issuer: String,
  authorize authorize: fn(Authorization) -> String,
  verify verify: fn(Exchange) -> Result(Verified, service.Error),
) -> Provider

A provider of your own. id names it in routes and cookies: lowercase letters, digits and hyphens, at most 32. name is shown on sign-in pages. issuer is the provider’s HTTPS URL; its accounts are recorded under it, kept apart from every built-in provider and SSO connection, so changing it later forgets every linked account. Two installed providers may not share one.

authorize returns the URL to send the browser to. verify redeems the code and returns the account; return Error(service.Unauthorized) for anything the provider refuses or that fails to parse. Bound the size of responses you read, and do not log them: they carry the user’s tokens. Any provider access token is yours to discard; Howdy stores none.

let assert Ok(identity) =
  auth.with_provider(identity, provider.custom(
    id: "gitea",
    name: "Gitea",
    issuer: "https://git.example.com",
    authorize: fn(request) {
      "https://git.example.com/login/oauth/authorize?" <> uri.query_to_string([
        #("client_id", client_id),
        #("redirect_uri", request.redirect_uri),
        #("response_type", "code"),
        #("state", request.state),
        #("code_challenge", request.challenge),
        #("code_challenge_method", "S256"),
      ])
    },
    verify: fn(exchange) { gitea_account(exchange) },
  ))
pub fn id(provider: Provider) -> String
pub fn name(provider: Provider) -> String
pub fn nonce_matches(exchange: Exchange, nonce: String) -> Bool

Whether an ID token’s nonce claim is the one this sign-in sent.

✨ Search Document