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}