Skip to main content

actix_jwt/
errors.rs

1//! JWT error types.
2//!
3//! All errors are represented by the [`JwtError`] enum which implements
4//! [`std::error::Error`], [`std::fmt::Display`] and
5//! [`actix_web::ResponseError`].  Error messages match the
6//! [`echo-jwt`](https://github.com/LdDl/echo-jwt) (Go implementation) sentinel
7//! errors for API-level compatibility.
8
9use std::fmt;
10
11use actix_web::HttpResponse;
12use actix_web::http::StatusCode;
13
14/// Unified error type for the JWT middleware.
15///
16/// Each variant maps to a specific HTTP status code via the
17/// [`actix_web::ResponseError`] implementation.
18///
19/// # Examples
20///
21/// ```
22/// use actix_jwt::JwtError;
23///
24/// let err = JwtError::ExpiredToken;
25/// assert_eq!(err.to_string(), "token is expired");
26/// ```
27///
28/// Variants that carry a message reproduce it verbatim:
29///
30/// ```
31/// use actix_jwt::JwtError;
32///
33/// let err = JwtError::TokenParsing("invalid signature".into());
34/// assert_eq!(err.to_string(), "invalid signature");
35/// assert!(err.is_token_parsing());
36/// ```
37#[derive(Debug)]
38pub enum JwtError {
39    /// HMAC secret key was not provided.
40    MissingSecretKey,
41    /// The authorizer callback rejected the request.
42    Forbidden,
43    /// No authenticator callback was configured.
44    MissingAuthenticator,
45    /// The request body could not be parsed into login credentials.
46    MissingLoginValues,
47    /// The authenticator callback returned an error.
48    FailedAuthentication,
49    /// JWT signing or encoding failed.
50    FailedTokenCreation,
51    /// The access token's `exp` claim is in the past.
52    ExpiredToken,
53    /// The `Authorization` header is present but empty.
54    EmptyAuthHeader,
55    /// The `exp` claim is missing from the token.
56    MissingExpField,
57    /// The `exp` claim is not a numeric value.
58    WrongFormatOfExp,
59    /// The `Authorization` header does not match the expected format.
60    InvalidAuthHeader,
61    /// The query-string token source was empty.
62    EmptyQueryToken,
63    /// The cookie token source was empty.
64    EmptyCookieToken,
65    /// The path-parameter token source was empty.
66    EmptyParamToken,
67    /// The configured signing algorithm is not supported.
68    InvalidSigningAlgorithm,
69    /// The private key file could not be read.
70    NoPrivKeyFile,
71    /// The public key file could not be read.
72    NoPubKeyFile,
73    /// The private key could not be parsed.
74    InvalidPrivKey,
75    /// The public key could not be parsed.
76    InvalidPubKey,
77    /// No `refresh_token` was found in the request.
78    MissingRefreshToken,
79    /// The refresh token failed validation.
80    InvalidRefreshToken,
81    /// The refresh token was not found in the store.
82    RefreshTokenNotFound,
83    /// The refresh token has expired.
84    RefreshTokenExpired,
85    /// An empty token string was passed to a store method.
86    TokenEmpty,
87    /// The supplied expiry timestamp is in the past.
88    ExpiryInPast,
89    /// A token parsing / validation error with a free-form message.
90    TokenParsing(String),
91    /// A token extraction error with a free-form message.
92    TokenExtraction(String),
93    /// An internal / infrastructure error with a free-form message.
94    Internal(String),
95}
96
97impl fmt::Display for JwtError {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::MissingSecretKey => write!(f, "secret key is required"),
101            Self::Forbidden => {
102                write!(f, "you don't have permission to access this resource")
103            }
104            Self::MissingAuthenticator => write!(f, "authenticator func is undefined"),
105            Self::MissingLoginValues => write!(f, "missing Username or Password"),
106            Self::FailedAuthentication => write!(f, "incorrect Username or Password"),
107            Self::FailedTokenCreation => write!(f, "failed to create JWT Token"),
108            Self::ExpiredToken => write!(f, "token is expired"),
109            Self::EmptyAuthHeader => write!(f, "auth header is empty"),
110            Self::MissingExpField => write!(f, "missing exp field"),
111            Self::WrongFormatOfExp => write!(f, "exp must be float64 format"),
112            Self::InvalidAuthHeader => write!(f, "auth header is invalid"),
113            Self::EmptyQueryToken => write!(f, "query token is empty"),
114            Self::EmptyCookieToken => write!(f, "cookie token is empty"),
115            Self::EmptyParamToken => write!(f, "parameter token is empty"),
116            Self::InvalidSigningAlgorithm => write!(f, "invalid signing algorithm"),
117            Self::NoPrivKeyFile => write!(f, "private key file unreadable"),
118            Self::NoPubKeyFile => write!(f, "public key file unreadable"),
119            Self::InvalidPrivKey => write!(f, "private key invalid"),
120            Self::InvalidPubKey => write!(f, "public key invalid"),
121            Self::MissingRefreshToken => write!(f, "missing refresh_token parameter"),
122            Self::InvalidRefreshToken => write!(f, "invalid or expired refresh token"),
123            Self::RefreshTokenNotFound => write!(f, "refresh token not found"),
124            Self::RefreshTokenExpired => write!(f, "refresh token expired"),
125            Self::TokenEmpty => write!(f, "token cannot be empty"),
126            Self::ExpiryInPast => write!(f, "token expiry time must be in the future"),
127            Self::TokenParsing(msg) => write!(f, "{msg}"),
128            Self::TokenExtraction(msg) => write!(f, "{msg}"),
129            Self::Internal(msg) => write!(f, "{msg}"),
130        }
131    }
132}
133
134impl std::error::Error for JwtError {}
135
136impl JwtError {
137    /// Returns `true` for the [`TokenParsing`](Self::TokenParsing) variant.
138    ///
139    /// # Examples
140    ///
141    /// ```
142    /// use actix_jwt::JwtError;
143    ///
144    /// assert!(JwtError::TokenParsing("bad".into()).is_token_parsing());
145    /// assert!(!JwtError::Forbidden.is_token_parsing());
146    /// ```
147    pub fn is_token_parsing(&self) -> bool {
148        matches!(self, Self::TokenParsing(_))
149    }
150
151    /// Returns `true` for the [`TokenExtraction`](Self::TokenExtraction) variant.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use actix_jwt::JwtError;
157    ///
158    /// assert!(JwtError::TokenExtraction("empty".into()).is_token_extraction());
159    /// assert!(!JwtError::ExpiredToken.is_token_extraction());
160    /// ```
161    pub fn is_token_extraction(&self) -> bool {
162        matches!(self, Self::TokenExtraction(_))
163    }
164
165    /// Returns `true` for the [`Forbidden`](Self::Forbidden) variant.
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use actix_jwt::JwtError;
171    ///
172    /// assert!(JwtError::Forbidden.is_forbidden());
173    /// assert!(!JwtError::ExpiredToken.is_forbidden());
174    /// ```
175    pub fn is_forbidden(&self) -> bool {
176        matches!(self, Self::Forbidden)
177    }
178}
179
180impl actix_web::ResponseError for JwtError {
181    fn status_code(&self) -> StatusCode {
182        match self {
183            Self::Forbidden => StatusCode::FORBIDDEN,
184
185            Self::MissingSecretKey
186            | Self::MissingAuthenticator
187            | Self::FailedTokenCreation
188            | Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
189
190            Self::MissingLoginValues
191            | Self::EmptyAuthHeader
192            | Self::MissingExpField
193            | Self::WrongFormatOfExp
194            | Self::InvalidAuthHeader
195            | Self::EmptyQueryToken
196            | Self::EmptyCookieToken
197            | Self::EmptyParamToken
198            | Self::MissingRefreshToken
199            | Self::TokenExtraction(_) => StatusCode::BAD_REQUEST,
200
201            Self::FailedAuthentication
202            | Self::ExpiredToken
203            | Self::InvalidSigningAlgorithm
204            | Self::NoPrivKeyFile
205            | Self::NoPubKeyFile
206            | Self::InvalidPrivKey
207            | Self::InvalidPubKey
208            | Self::InvalidRefreshToken
209            | Self::RefreshTokenNotFound
210            | Self::RefreshTokenExpired
211            | Self::TokenEmpty
212            | Self::ExpiryInPast
213            | Self::TokenParsing(_) => StatusCode::UNAUTHORIZED,
214        }
215    }
216
217    fn error_response(&self) -> HttpResponse {
218        HttpResponse::build(self.status_code()).json(serde_json::json!({
219            "code": self.status_code().as_u16(),
220            "message": self.to_string(),
221        }))
222    }
223}