OAuth2-rs (Rust)
Authentication Library
OAuth2-rs (Rust)
Overview
OAuth2-rs is an extensible, strongly-typed OAuth2 client library for the Rust programming language. It provides a complete implementation of the OAuth2 protocol compliant with RFC 6749, supporting both async and sync I/O. The library offers comprehensive OAuth2 flow functionality including obtaining access tokens, state validation, and refresh token acquisition, all implemented following security best practices.
Details
OAuth2-rs stands as the most mature OAuth2 library in the Rust ecosystem, actively developed and maintained as of 2024. Key characteristics include safety assurance through Rust's strong type system, support for modern asynchronous programming with async/await, and compliance with modern security standards like PKCE.
Since version 5.0, an endpoint typestate system has been introduced, allowing compile-time tracking of OAuth2 endpoint configuration states. This prevents runtime errors due to configuration issues and enables writing safer code. The MSRV (Minimum Supported Rust Version) is 1.65, with support for integration with popular HTTP clients like reqwest and curl.
Pros and Cons
Pros
- Strong Type Safety: Prevents configuration errors using Rust's type system
- RFC Compliant: Complete implementation of OAuth2.0 specification (RFC 6749)
- Async/Sync Support: Supports both async/await and synchronous processing
- PKCE Support: Complies with latest security standards
- Customizable: Flexible configuration of HTTP clients and endpoints
- Active Development: Actively updated and maintained as of 2024
Cons
- Learning Curve: Requires understanding of Rust's type system and complex generics
- Configuration Complexity: Initial setup can be complex due to the typestate system
- Compile Time: Slight increase in compile time due to numerous generics
- Documentation: Limited Japanese resources
Reference Pages
Code Examples
Basic OAuth2 Client Configuration
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
PkceCodeChallenge, RedirectUrl, Scope, TokenUrl
};
use oauth2::basic::BasicClient;
// Initialize OAuth2 client
let client = BasicClient::new(
ClientId::new("your-client-id".to_string()),
Some(ClientSecret::new("your-client-secret".to_string())),
AuthUrl::new("https://provider.com/oauth2/authorize".to_string())?,
Some(TokenUrl::new("https://provider.com/oauth2/token".to_string())?)
)
.set_redirect_uri(RedirectUrl::new("http://localhost:8080/callback".to_string())?);
Authorization Code Grant Flow
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
RedirectUrl, Scope, TokenUrl, TokenResponse, reqwest::async_http_client
};
use oauth2::basic::{BasicClient, BasicTokenType};
use url::Url;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Client configuration
let client = BasicClient::new(
ClientId::new("your-client-id".to_string()),
Some(ClientSecret::new("your-client-secret".to_string())),
AuthUrl::new("https://provider.com/oauth2/authorize".to_string())?,
Some(TokenUrl::new("https://provider.com/oauth2/token".to_string())?)
)
.set_redirect_uri(RedirectUrl::new("http://localhost:8080/callback".to_string())?);
// Generate authorization URL
let (auth_url, csrf_token) = client
.authorize_url(CsrfToken::new_random)
.add_scope(Scope::new("read".to_string()))
.add_scope(Scope::new("write".to_string()))
.url();
println!("Authorization URL: {}", auth_url);
// Callback handling (in real applications, handled by web server)
// let code = AuthorizationCode::new("received-auth-code".to_string());
// let csrf_token_received = CsrfToken::new("received-csrf-token".to_string());
// Token exchange
let token_result = client
.exchange_code(AuthorizationCode::new("auth-code".to_string()))
.request_async(async_http_client)
.await?;
println!("Access Token: {}", token_result.access_token().secret());
Ok(())
}
Authentication Flow with PKCE
use oauth2::{
AuthUrl, AuthorizationCode, ClientId, CsrfToken, PkceCodeChallenge,
PkceCodeVerifier, RedirectUrl, Scope, TokenUrl, reqwest::async_http_client
};
use oauth2::basic::BasicClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = BasicClient::new(
ClientId::new("your-client-id".to_string()),
None, // Client secret not required
AuthUrl::new("https://provider.com/oauth2/authorize".to_string())?,
Some(TokenUrl::new("https://provider.com/oauth2/token".to_string())?)
)
.set_redirect_uri(RedirectUrl::new("http://localhost:8080/callback".to_string())?);
// Generate PKCE challenge and verifier
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
// Generate authorization URL (with PKCE)
let (auth_url, csrf_token) = client
.authorize_url(CsrfToken::new_random)
.add_scope(Scope::new("openid".to_string()))
.set_pkce_challenge(pkce_challenge)
.url();
println!("Authorization URL (PKCE): {}", auth_url);
// Token exchange (with PKCE verification)
let token_result = client
.exchange_code(AuthorizationCode::new("auth-code".to_string()))
.set_pkce_verifier(pkce_verifier)
.request_async(async_http_client)
.await?;
println!("Access Token: {}", token_result.access_token().secret());
Ok(())
}
Client Credentials Grant Flow
use oauth2::{
ClientCredentialsTokenRequest, ClientId, ClientSecret, Scope,
TokenUrl, reqwest::async_http_client
};
use oauth2::basic::BasicClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = BasicClient::new(
ClientId::new("your-client-id".to_string()),
Some(ClientSecret::new("your-client-secret".to_string())),
AuthUrl::new("https://provider.com/oauth2/authorize".to_string())?,
Some(TokenUrl::new("https://provider.com/oauth2/token".to_string())?)
);
// Obtain token using Client Credentials Grant
let token_result = client
.exchange_client_credentials()
.add_scope(Scope::new("api:read".to_string()))
.request_async(async_http_client)
.await?;
println!("Client Access Token: {}", token_result.access_token().secret());
Ok(())
}
Authentication with Refresh Token
use oauth2::{
RefreshToken, Scope, TokenResponse, reqwest::async_http_client
};
use oauth2::basic::BasicClient;
async fn refresh_access_token(
client: &BasicClient,
refresh_token: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let token_result = client
.exchange_refresh_token(&RefreshToken::new(refresh_token.to_string()))
.add_scope(Scope::new("read".to_string()))
.request_async(async_http_client)
.await?;
println!("New Access Token: {}", token_result.access_token().secret());
// New refresh token may be included
if let Some(new_refresh_token) = token_result.refresh_token() {
println!("New Refresh Token: {}", new_refresh_token.secret());
}
Ok(())
}
Custom HTTP Client Usage
use oauth2::{HttpRequest, HttpResponse, async_http_client};
use std::future::Future;
use std::pin::Pin;
// Custom async HTTP client
fn custom_async_http_client(
request: HttpRequest,
) -> Pin<Box<dyn Future<Output = Result<HttpResponse, reqwest::Error>> + Send>> {
Box::pin(async move {
// Add custom headers or logging functionality
let client = reqwest::Client::new();
let response = client
.request(request.method, &request.url.to_string())
.headers(request.headers)
.body(request.body)
.send()
.await?;
// Response conversion
Ok(HttpResponse {
status_code: response.status(),
headers: response.headers().clone(),
body: response.bytes().await?.to_vec(),
})
})
}
// Usage example
let token_result = client
.exchange_code(AuthorizationCode::new("auth-code".to_string()))
.request_async(custom_async_http_client)
.await?;
Error Handling
use oauth2::{
RequestTokenError, basic::BasicErrorResponse, StandardErrorResponse
};
async fn handle_oauth_errors() {
match client.exchange_code(code).request_async(async_http_client).await {
Ok(token) => {
println!("Authentication successful: {}", token.access_token().secret());
}
Err(RequestTokenError::ServerResponse(err)) => {
println!("Server error: {:?}", err);
}
Err(RequestTokenError::Request(err)) => {
println!("Request error: {:?}", err);
}
Err(RequestTokenError::Parse(err, response)) => {
println!("Parse error: {:?}, Response: {:?}", err, response);
}
Err(RequestTokenError::Other(err)) => {
println!("Other error: {:?}", err);
}
}
}
Safe Configuration with Typestates
use oauth2::{
AuthUrl, TokenUrl, ClientId, ClientSecret, EndpointSet, EndpointNotSet
};
use oauth2::basic::BasicClient;
// Explicitly specify endpoint typestates
type MyClient = BasicClient<
EndpointSet, // HasAuthUrl
EndpointNotSet, // HasDeviceAuthUrl
EndpointNotSet, // HasIntrospectionUrl
EndpointNotSet, // HasRevocationUrl
EndpointSet, // HasTokenUrl
>;
fn create_configured_client() -> MyClient {
BasicClient::new(
ClientId::new("client-id".to_string()),
Some(ClientSecret::new("client-secret".to_string())),
AuthUrl::new("https://example.com/auth".to_string()).unwrap(),
Some(TokenUrl::new("https://example.com/token".to_string()).unwrap())
)
}