Skip to main content

fastmcp_core/
auth.rs

1//! Authentication context and access token helpers.
2//!
3//! This module provides lightweight types for representing authenticated
4//! request context. It is transport-agnostic and can be populated by
5//! server-side authentication providers.
6
7use serde::{Deserialize, Serialize};
8
9/// Session state key used to store authentication context.
10pub const AUTH_STATE_KEY: &str = "fastmcp.auth";
11
12/// Parsed access token (scheme + token value).
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct AccessToken {
15    /// Token scheme (e.g., "Bearer").
16    pub scheme: String,
17    /// Raw token value.
18    pub token: String,
19}
20
21impl AccessToken {
22    /// Attempts to parse an Authorization header value.
23    ///
24    /// Accepts formats like:
25    /// - `Bearer <token>`
26    /// - `<token>` (treated as bearer)
27    #[must_use]
28    pub fn parse(value: &str) -> Option<Self> {
29        let trimmed = value.trim();
30        if trimmed.is_empty() {
31            return None;
32        }
33
34        if let Some((scheme, token)) = trimmed.split_once(' ') {
35            let scheme = scheme.trim();
36            let token = token.trim();
37            if scheme.is_empty() || token.is_empty() {
38                return None;
39            }
40            return Some(Self {
41                scheme: scheme.to_string(),
42                token: token.to_string(),
43            });
44        }
45
46        Some(Self {
47            scheme: "Bearer".to_string(),
48            token: trimmed.to_string(),
49        })
50    }
51}
52
53/// Authentication context stored for a request/session.
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
55pub struct AuthContext {
56    /// Subject identifier (user or client ID).
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub subject: Option<String>,
59    /// Authorized scopes for this subject.
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub scopes: Vec<String>,
62    /// Access token (if available).
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub token: Option<AccessToken>,
65    /// Optional raw claims (transport or provider specific).
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub claims: Option<serde_json::Value>,
68}
69
70impl AuthContext {
71    /// Creates an anonymous context (no subject, no scopes).
72    #[must_use]
73    pub fn anonymous() -> Self {
74        Self::default()
75    }
76
77    /// Creates a context with a subject identifier.
78    #[must_use]
79    pub fn with_subject(subject: impl Into<String>) -> Self {
80        Self {
81            subject: Some(subject.into()),
82            ..Self::default()
83        }
84    }
85}