Expand description
§Core trait for implementing OpenStack authentication plugins to [openstack_sdk]
This crate provides the foundational types and traits required to authenticate against the OpenStack Identity service (Keystone). It defines:
OpenStackAuthType— the primary trait that all authentication plugins must implement to enable login flows (password, token, JWT, WebSSO, etc.).OpenStackMultifactorAuthMethod— the trait for multifactor-capable methods that can be composed into multipass/multifactor authentication requests.AuthToken— the structure that represents a successful Keystone authentication result, including the bearer token and parsed server response.AuthTokenScope— represents the authorization scope attached to a token (project, domain, system, or unscoped).AuthError— the unified error type covering all authentication-related failures, including receipts for multipass flows.Auth— the enum that wraps the current authentication state (token or none).AuthState— describes token validity (valid, expired, about-to-expire, or unset).
§Plugin Registration
Authentication plugins are registered at compile time using Rust’s inventory crate.
Implement OpenStackAuthType and submit an AuthPluginRegistration via
inventory::submit!{}. For multipass support, additionally implement
OpenStackMultifactorAuthMethod and submit an AuthMethodPluginRegistration.
The execute_auth_request function provides a common, instrumented pathway
for sending authentication HTTP requests with timing and request-id logging.
§Examples
§Basic Authentication
Authenticate by creating an AuthToken from an existing token string
(e.g., obtained from an environment variable or a previous login):
use secrecy::{ExposeSecret, SecretString};
use openstack_sdk_auth_core::{AuthToken, AuthTokenScope};
let token = AuthToken::new(SecretString::from("my-token"), None);
let scope = AuthTokenScope::Unscoped;
println!("Scope: {:?}", token.get_scope());
println!("State: {:?}", token.get_state(None));§Checking Token State
Tokens can be checked for validity with an optional expiration threshold:
use chrono::TimeDelta;
use openstack_sdk_auth_core::AuthState;
let offset = TimeDelta::minutes(5);
match auth.get_state(Some(offset)) {
AuthState::Valid => println!("Token is valid"),
AuthState::AboutToExpire => println!("Token will expire within 5 minutes"),
AuthState::Expired => println!("Token has expired"),
AuthState::Unset => println!("No token data available"),
}§Setting Request Headers
The Auth type can inject the X-Auth-Token header into outgoing HTTP requests:
use http::HeaderMap;
let mut headers = HeaderMap::new();
auth.set_header(&mut headers)?;
// headers now contains the `X-Auth-Token` header if the auth type is a token.§Implementing a Custom Authenticator
Creating a new authentication method requires implementing OpenStackAuthType:
use std::collections::HashMap;
use async_trait::async_trait;
use secrecy::SecretString;
use serde_json::{Value, json};
use openstack_sdk_auth_core::{Auth, AuthError, AuthToken, AuthTokenScope, OpenStackAuthType};
pub struct MyAuthenticator;
static PLUGIN: MyAuthenticator = MyAuthenticator;
inventory::submit! {
openstack_sdk_auth_core::AuthPluginRegistration { method: &PLUGIN }
}
#[async_trait]
impl OpenStackAuthType for MyAuthenticator {
fn get_supported_auth_methods(&self) -> Vec<&'static str> {
vec!["v3myauth", "myauth"]
}
fn requirements(&self, _hints: Option<&Value>) -> Result<Value, AuthError> {
Ok(json!({
"type": "object",
"required": ["token_id"],
"properties": {
"token_id": {
"type": "string",
"description": "The token identifier"
}
}
}))
}
fn api_version(&self) -> (u8, u8) {
(3, 0)
}
async fn auth(
&self,
_http_client: &reqwest::Client,
_identity_url: &url::Url,
_values: &HashMap<String, SecretString>,
_scope: Option<&AuthTokenScope>,
_hints: Option<&Value>,
) -> Result<Auth, AuthError> {
// Perform your authentication logic here and return an AuthToken
let auth_token = AuthToken::new("example-token", None);
Ok(Auth::AuthToken(Box::new(auth_token)))
}
}§Error Handling Patterns
The AuthError enum provides different error variants that you can match on
to handle specific failure scenarios:
use openstack_sdk_auth_core::{AuthError, Auth, AuthToken, AuthTokenScope};
match simulate_auth_result() {
Ok(Auth::AuthToken(token)) => {
println!("Authentication successful");
println!("Token expires at: {:?}", token.get_state(None));
}
Ok(Auth::None) => {
println!("No authentication available");
}
Ok(auth) => {
println!("Unknown auth type: {:?}", auth);
}
Err(AuthError::AuthReceipt(receipt)) => {
println!("Multifactor authentication required");
let methods: Vec<_> = receipt.required_auth_methods.iter()
.flatten()
.cloned()
.collect();
println!("Required methods: {:?}", methods);
}
Err(AuthError::Serde { .. }) => {
println!("Failed to parse response (malformed JSON)");
}
Err(AuthError::UnknownAuth { code, message }) => {
println!("Unknown authentication error (code: {})", code);
if let Some(msg) = &message {
println!("Message: {}", msg);
}
}
Err(e) => {
println!("Authentication failed: {}", e);
}
}§Implementing a Multifactor-Authenticator
To support multifactor authentication (e.g., TOTP or password + token),
implement both OpenStackAuthType and OpenStackMultifactorAuthMethod:
use std::collections::HashMap;
use async_trait::async_trait;
use secrecy::{SecretString, ExposeSecret};
use serde_json::{Value, json};
use openstack_sdk_auth_core::{
Auth, AuthError, AuthToken, AuthTokenScope,
OpenStackAuthType, OpenStackMultifactorAuthMethod,
};
pub struct MyMultifactorAuthenticator;
static PLUGIN: MyMultifactorAuthenticator = MyMultifactorAuthenticator;
inventory::submit! {
openstack_sdk_auth_core::AuthPluginRegistration { method: &PLUGIN }
}
inventory::submit! {
openstack_sdk_auth_core::AuthMethodPluginRegistration { method: &PLUGIN }
}
#[async_trait]
impl OpenStackMultifactorAuthMethod for MyMultifactorAuthenticator {
fn get_supported_auth_methods(&self) -> Vec<&'static str> {
vec!["v3myauth", "myauth"]
}
fn requirements(&self, _hints: Option<&Value>) -> Result<Value, AuthError> {
Ok(json!({
"type": "object",
"required": ["auth_code"], // Additional auth method requirement
"properties": {
"auth_code": {
"type": "string",
"format": "password",
"description": "One-time authentication code"
}
}
}))
}
/// Extracts authentication data from the values map.
fn get_auth_data(
&self,
values: &HashMap<String, SecretString>,
) -> Result<(&'static str, Value), AuthError> {
let auth_code = values
.get("auth_code")
.ok_or_else(|| AuthError::AuthValueNotSupplied("auth_code".to_string()))?;
Ok(("myauth", json!({
"auth_code": auth_code.expose_secret()
})))
}
}
#[async_trait]
impl OpenStackAuthType for MyMultifactorAuthenticator {
fn get_supported_auth_methods(&self) -> Vec<&'static str> {
vec!["v3myauth", "myauth"]
}
fn requirements(&self, _hints: Option<&Value>) -> Result<Value, AuthError> {
Ok(json!({
"type": "object",
"required": ["auth_code"],
"properties": {
"auth_code": {
"type": "string",
"format": "password",
"description": "One-time authentication code"
}
}
}))
}
fn api_version(&self) -> (u8, u8) {
(3, 0)
}
async fn auth(
&self,
_http_client: &reqwest::Client,
_identity_url: &url::Url,
_values: &HashMap<String, SecretString>,
_scope: Option<&AuthTokenScope>,
_hints: Option<&Value>,
) -> Result<Auth, AuthError> {
// Perform multifactor authentication and return an AuthToken:
let auth_token = AuthToken::new("example-token", None);
Ok(Auth::AuthToken(Box::new(auth_token)))
}
}§Token Scoping
Tokens can be scoped to specific projects or domains using AuthTokenScope:
use openstack_sdk_auth_core::{AuthTokenScope};
use openstack_sdk_auth_core::types::{Project, Domain};
// Project-scope by ID
let project_scope = AuthTokenScope::Project(Project {
id: Some("project-id-123".to_string()),
name: None,
domain: None,
});
// Domain-scope with domain name
let domain_scope = AuthTokenScope::Domain(Domain {
id: None,
name: Some("Default".to_string()),
});
// Unscoped (default)
let unscoped: AuthTokenScope = AuthTokenScope::default();§Handling Authentication Receipts
When multifactor authentication is enabled, Keystone returns an authentication receipt instead of a new token. The receipt contains information about what additional authentication methods are required:
use openstack_sdk_auth_core::{AuthTokenScope, AuthError, Auth, AuthReceiptResponse, AuthReceipt};
use chrono::Local;
let scope = Some(&AuthTokenScope::Unscoped);
match simulate_auth_result() {
Ok(auth) => {
match auth {
Auth::AuthToken(token) => {
println!("Authenticated with token");
println!("Token: {:?}", token.get_scope());
}
Auth::None => {
println!("Not authenticated");
}
_ => {
println!("Unknown auth type");
}
}
}
Err(AuthError::AuthReceipt(receipt)) => {
println!("Additional authentication methods required");
println!("Required methods: {:?}", receipt.required_auth_methods);
let methods: Vec<_> = receipt.receipt.methods.iter().cloned().collect();
println!("Already completed methods: {:?}", methods);
// Use the receipt token for subsequent authentication requests
if let Some(receipt_token) = &receipt.token {
println!("Receipt token: {}...", &receipt_token.chars().take(8).collect::<String>());
}
}
Err(e) => {
println!("Authentication error: {}", e);
}
}§Token State Management
The AuthToken::get_state method can be used to determine if a token is valid and
needs to be refreshed. Using an expiration offset allows you to proactively refresh
tokens before they expire:
use chrono::TimeDelta;
use openstack_sdk_auth_core::{AuthToken, AuthState, types::{AuthResponse, TokenInfo, User, Project}};
use secrecy::SecretString;
// Create a token with expiration info
let auth_info = AuthResponse {
token: TokenInfo {
expires_at: chrono::Utc::now() + TimeDelta::hours(24),
user: User::default(),
..Default::default()
},
};
let token = AuthToken::new("my-token", Some(auth_info));
// Check with 5-minute buffer for proactive refresh
match token.get_state(Some(TimeDelta::minutes(5))) {
AuthState::Valid => {
println!("Token is valid");
}
AuthState::AboutToExpire => {
println!("Token will expire soon, refreshing...");
// Refresh the token before it expires
}
AuthState::Expired => {
println!("Token has expired, re-authenticating...");
}
AuthState::Unset => {
println!("No token data available");
}
}Re-exports§
pub use authtoken::AuthToken;pub use authtoken::AuthTokenError;pub use authtoken_scope::AuthTokenScope;pub use types::*;
Modules§
- auth_
helper - Authentication helper.
- authtoken
- OpenStack AuthToken based authorization (X-Auth-Token)
- authtoken_
scope - OpenStack AuthToken Scope handling
- types
- Types of the SDK authentication methods
Structs§
- Auth
Method Plugin Registration - Registry entry for multifactor-capable authentication methods.
- Auth
Plugin Registration - Registry entry for authentication plugins.
Enums§
- Auth
- An OpenStack Authentication type
- Auth
Error - Authentication error.
- Auth
State - Authentication state enum
- Builder
Error - Builder error.
Traits§
- Open
Stack Auth Type - The trait for authentication “plugins” for enabling OpenStack authentication.
- Open
Stack Multifactor Auth Method - The trait for multifactor-capable authentication methods.
Functions§
- anchor_
plugins - Force the linker to include all auth plugin crates by iterating over the
inventory registries. Must be called during client initialization since
plugin crates are otherwise stripped by the linker (no other code
references them), causing
AuthTypelookups to fail at runtime. - execute_
auth_ request