Rust SDK

Official OAuth42 SDK for Rust. Works with Actix Web, Axum, Rocket, and any Rust web framework.

Installation

Add to your Cargo.toml:

[dependencies]
o42sdk = "0.1"

With framework support:

[dependencies]
o42sdk = { version = "0.1", features = ["actix", "axum"] }

Quick Start

use o42sdk::{OAuth42Client, Config};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create client with builder pattern
    let mut client = OAuth42Client::builder()
        .issuer("https://api.oauth42.com")?
        .client_id("your_client_id")
        .client_secret("your_client_secret")
        .redirect_uri("https://yourapp.com/callback")?
        .scopes(vec!["openid", "profile", "email"])
        .build()?;

    // Discover endpoints automatically
    client.discover().await?;

    // Generate authorization URL with PKCE
    let (auth_url, code_verifier) = client
        .authorize_url().await?
        .with_pkce()
        .build()?;

    println!("Visit: {}", auth_url);

    // After user authorizes, exchange code for tokens
    let tokens = client
        .exchange_code(&code, Some(&code_verifier))
        .await?;

    println!("Access token: {}", tokens.access_token);

    Ok(())
}

Actix Web Integration

use actix_web::{web, App, HttpResponse, HttpServer};
use actix_session::{Session, SessionMiddleware, storage::CookieSessionStore};
use actix_web::cookie::Key;
use o42sdk::{OAuth42Client, Config};

async fn login(
    client: web::Data<OAuth42Client>,
    session: Session,
) -> Result<HttpResponse, actix_web::Error> {
    let mut client = client.as_ref().clone();

    let (auth_url, code_verifier) = client
        .authorize_url().await
        .map_err(actix_web::error::ErrorInternalServerError)?
        .with_pkce()
        .build()
        .map_err(actix_web::error::ErrorInternalServerError)?;

    session.insert("code_verifier", code_verifier)?;

    Ok(HttpResponse::Found()
        .append_header(("Location", auth_url.to_string()))
        .finish())
}

async fn callback(
    client: web::Data<OAuth42Client>,
    session: Session,
    query: web::Query<CallbackQuery>,
) -> Result<HttpResponse, actix_web::Error> {
    let code_verifier = session
        .get::<String>("code_verifier")?
        .ok_or_else(|| actix_web::error::ErrorUnauthorized("No code verifier"))?;

    let mut client = client.as_ref().clone();

    let tokens = client
        .exchange_code(&query.code, Some(&code_verifier))
        .await
        .map_err(actix_web::error::ErrorInternalServerError)?;

    session.insert("access_token", tokens.access_token)?;

    Ok(HttpResponse::Found()
        .append_header(("Location", "/dashboard"))
        .finish())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let client = OAuth42Client::builder()
        .issuer("https://api.oauth42.com").unwrap()
        .client_id(std::env::var("OAUTH42_CLIENT_ID").unwrap())
        .client_secret(std::env::var("OAUTH42_CLIENT_SECRET").unwrap())
        .redirect_uri("https://yourapp.com/callback").unwrap()
        .scopes(vec!["openid", "profile", "email"])
        .build()
        .unwrap();

    let client_data = web::Data::new(client);

    HttpServer::new(move || {
        App::new()
            .app_data(client_data.clone())
            .wrap(SessionMiddleware::new(
                CookieSessionStore::default(),
                Key::generate(),
            ))
            .route("/login", web::get().to(login))
            .route("/callback", web::get().to(callback))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

#[derive(serde::Deserialize)]
struct CallbackQuery {
    code: String,
}

Axum Integration

use axum::{
    extract::{Query, State},
    http::StatusCode,
    response::{IntoResponse, Redirect},
    routing::get,
    Router,
};
use axum_extra::extract::cookie::{Cookie, CookieJar};
use o42sdk::OAuth42Client;
use std::sync::Arc;
use tokio::sync::Mutex;

type AppState = Arc<Mutex<OAuth42Client>>;

async fn login(
    State(client): State<AppState>,
    jar: CookieJar,
) -> Result<(CookieJar, Redirect), StatusCode> {
    let mut client = client.lock().await;

    let (auth_url, code_verifier) = client
        .authorize_url().await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
        .with_pkce()
        .build()
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let jar = jar.add(Cookie::new("code_verifier", code_verifier));

    Ok((jar, Redirect::to(auth_url.as_str())))
}

#[derive(serde::Deserialize)]
struct CallbackParams {
    code: String,
}

async fn callback(
    State(client): State<AppState>,
    jar: CookieJar,
    Query(params): Query<CallbackParams>,
) -> Result<(CookieJar, Redirect), StatusCode> {
    let code_verifier = jar
        .get("code_verifier")
        .ok_or(StatusCode::UNAUTHORIZED)?
        .value()
        .to_string();

    let mut client = client.lock().await;

    let tokens = client
        .exchange_code(&params.code, Some(&code_verifier))
        .await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let jar = jar.add(Cookie::new("access_token", tokens.access_token));

    Ok((jar, Redirect::to("/dashboard")))
}

#[tokio::main]
async fn main() {
    let client = OAuth42Client::builder()
        .issuer("https://api.oauth42.com").unwrap()
        .client_id(std::env::var("OAUTH42_CLIENT_ID").unwrap())
        .client_secret(std::env::var("OAUTH42_CLIENT_SECRET").unwrap())
        .redirect_uri("https://yourapp.com/callback").unwrap()
        .scopes(vec!["openid", "profile", "email"])
        .build()
        .unwrap();

    let state = Arc::new(Mutex::new(client));

    let app = Router::new()
        .route("/login", get(login))
        .route("/callback", get(callback))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
        .await
        .unwrap();

    axum::serve(listener, app).await.unwrap();
}

Features

Type Safety

Full type safety with Result types and compile-time guarantees.

Async/Await

Built on Tokio for high-performance async operations.

Framework Support

Optional integrations for Actix Web, Axum, and Rocket.

Zero-Cost Abstractions

Minimal runtime overhead with compile-time optimizations.

Best Practices

Use Environment Variables

Store credentials securely using std::env or dotenvy crate.

Error Handling

Use Result types and the ? operator for idiomatic error handling.

Enable PKCE

Always use .with_pkce() for enhanced security.

TLS/SSL

Uses rustls for secure TLS connections by default.

Next Steps