axum-security-oauth2 0.0.2

A minimal OAuth2 client library, part of the axum-security workspace
Documentation

A minimal OAuth2 client for the authorization code flow (RFC 6749), written for the axum-security family but usable on its own.

Compared to the oauth2 crate this one has no typestate and no type parameters: configuration is validated once at build(), so client calls only fail for reasons that can occur at request time. PKCE (RFC 7636) is on for the default start_login/ finish_login pair; explicit _non_pkce variants exist for providers that reject the PKCE parameters. Values are plain String, [url::Url] or [std::time::Duration], with one wrapper: [CsrfToken], whose == compares in constant time. Secrets stay out of logs because every crate type that holds one (the client, [Login], [Tokens], [CsrfToken], errors) redacts it in its Debug output — but a secret you store is a plain string, so keep it out of your own Debug/Display impls.

Features

  • reqwest (default) — the [reqwest] backend for the [HttpClient] enum, plus a default client (no redirects, 10s timeout). Without any backend feature try_build fails with [ConfigError::NoHttpClient].
  • rustls (default) — TLS for the reqwest backend via rustls.
  • native-tls — TLS for the reqwest backend via the platform's native TLS library.

Example

use axum_security_oauth2::OAuth2Client;

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
// Provider shortcuts (github, google, microsoft, gitlab, discord, spotify, twitch)
// preset the endpoints; OAuth2Client::builder() takes them explicitly.
let client = OAuth2Client::github()
    .client_id("my-client-id")
    .client_secret("my-client-secret")
    .redirect_url("https://my-app.example/callback")
    .scopes(&["read:user"])
    .build(); // or try_build() to handle ConfigError

// Leg 1: redirect the user to `login.url`; persist the CSRF token
// and PKCE verifier (e.g. in a signed cookie) for the callback.
let login = client.start_login();
// Fields are owned and public — move them out, no clone needed.
let (url, csrf_token, pkce_verifier) = (login.url, login.csrf_token, login.pkce_verifier);

// Leg 2 (on the callback route): compare `csrf_token` with the `state`
// query parameter (constant-time via `==`), then exchange the code.
let state = "state-from-the-query-string";
assert!(csrf_token == state); // reject the callback if this fails
let code = "code-from-the-query-string";
let tokens = client.finish_login(code, &pkce_verifier).await?;
let _access_token = &tokens.access_token;

// Later: trade the refresh token for fresh tokens (RFC 6749 §6).
if let Some(refresh_token) = &tokens.refresh_token {
    let fresh = client.refresh_tokens(refresh_token).await?;
    let _fresh_access_token = &fresh.access_token;
}
# Ok(())
# }

Per-login extras (an oidc nonce, prompt, ...) go through start_login_with; providers that only take credentials in the request body are served by [AuthType::RequestBody].