Skip to content

OAuth2

English | 中文

OAuth2Manager covers authorization-code, refresh, password, and client-credentials flows. State is persisted through SaTokenDao. You can share Dao / key prefix with an existing SaTokenManager.

When to use

  • You run an authorization server that issues access / refresh tokens.
  • Public clients need PKCE.
  • Password grant: you implement PasswordVerifier; core never stores user passwords.

Create the manager

rust
use sa_token_core::oauth2::OAuth2Manager;
use sa_token_core::SaTokenManager;

// Align Dao with an existing Manager (recommended)
let oauth = OAuth2Manager::from_manager(&manager)
    .with_ttl(600, 3600, 2_592_000) // code / access / refresh (seconds)
    .with_require_pkce(false);

// Or OAuth2Manager::from_dao(dao) / OAuth2Manager::new(storage)

Register a client

Secrets are stored as Argon2 PHC. At registration you may set plaintext client_secret (in-memory only, not serialized) or a ready client_secret_hash.

rust
use sa_token_core::oauth2::OAuth2Client;

let client = OAuth2Client {
    client_id: "web-app".into(),
    client_secret: "plain-secret-at-register".into(),
    client_secret_hash: String::new(),
    redirect_uris: vec!["https://app.example/callback".into()],
    grant_types: vec![
        "authorization_code".into(),
        "refresh_token".into(),
    ],
    scope: vec!["openid".into(), "profile".into()],
    public_client: false,
};
oauth.register_client(&client).await?;
// or register_client_with_secret(client_id, plain_secret, ...)

Public clients: public_client = true, no secret; authorization-code requires PKCE.

Authorization code + PKCE

rust
use sa_token_core::oauth2::{PkceChallenge, TokenIssueRequest};

let pkce = PkceChallenge::from_verifier_s256(&code_verifier)?;
let code = oauth
    .issue_authorization_code(
        "web-app".into(),
        "user_42".into(),
        "https://app.example/callback".into(),
        vec!["openid".into()],
        Some(pkce),
        Some("state-xyz".into()),
    )
    .await?;

let token = oauth
    .issue_token(TokenIssueRequest {
        grant_type: "authorization_code".into(),
        client_id: "web-app".into(),
        client_secret: "plain-secret-at-register".into(),
        code: Some(code.code),
        redirect_uri: Some("https://app.example/callback".into()),
        code_verifier: Some(code_verifier),
        ..Default::default()
    })
    .await?;

You can also call exchange_code_for_token, refresh_access_token, and revoke_token. issue_token dispatches by grant_type.

Password grant

Implement PasswordVerifier and attach it:

rust
use async_trait::async_trait;
use sa_token_core::oauth2::PasswordVerifier;
use sa_token_core::{SaTokenError, SaTokenResult};
use std::sync::Arc;

struct MyVerifier;

#[async_trait]
impl PasswordVerifier for MyVerifier {
    async fn verify_password(&self, username: &str, password: &str) -> SaTokenResult<()> {
        if username == "alice" && password == "correct" {
            Ok(())
        } else {
            Err(SaTokenError::OAuth2InvalidCredentials)
        }
    }
}

let oauth = OAuth2Manager::from_manager(&manager)
    .with_password_verifier(Arc::new(MyVerifier));

let token = oauth
    .issue_token(TokenIssueRequest {
        grant_type: "password".into(),
        client_id: "web-app".into(),
        client_secret: "plain-secret-at-register".into(),
        username: Some("alice".into()),
        password: Some("correct".into()),
        scope: vec!["profile".into()],
        ..Default::default()
    })
    .await?;

Also available: client_credentials_grant / grant_type: "client_credentials".

Verify and revoke

rust
let info = oauth.verify_access_token(&token.access_token).await?;
oauth.revoke_token(&token.access_token).await?;

Pitfalls

TopicNote
DaoPrefer from_manager / from_dao so keys stay aligned
PKCERequired for public clients; with_require_pkce(true) forces it for confidential clients too
SecretsStorage keeps hashes; use verify_client for checks

MIT OR Apache-2.0