Skip to content

Commit ccf9880

Browse files
committed
feat: [#727] allow to authenticate API via authentication header
The API allos client authentication via a `token` parameter in the URL query: ```console curl http://0.0.0.0:1212/api/v1/stats?token=MyAccessToken | jq ``` Now it's also possible to do it via Authentication Header: ```console curl -H "Authorization: Bearer MyAccessToken" http://0.0.0.0:1212/api/v1/stats | jq ``` This is to avoid leaking the token in logs, proxies, etc. For now, it's only optional and recommendable. It could be mandatory in future major API versions.
1 parent 6a22b1e commit ccf9880

File tree

3 files changed

+298
-98
lines changed

3 files changed

+298
-98
lines changed

packages/axum-rest-tracker-api-server/src/v1/middlewares/auth.rs

+46-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
//! Authentication middleware for the API.
22
//!
3-
//! It uses a "token" GET param to authenticate the user. URLs must be of the
4-
//! form:
3+
//! It uses a "token" to authenticate the user. The token must be one of the
4+
//! `access_tokens` in the tracker [HTTP API configuration](torrust_tracker_configuration::HttpApi).
5+
//!
6+
//! There are two ways to provide the token:
7+
//!
8+
//! 1. As a `Bearer` token in the `Authorization` header.
9+
//! 2. As a `token` GET param in the URL.
10+
//!
11+
//! Using the `Authorization` header:
12+
//!
13+
//! ```console
14+
//! curl -H "Authorization: Bearer MyAccessToken" http://<host>:<port>/api/v1/<context>
15+
//! ```
16+
//!
17+
//! Using the `token` GET param:
518
//!
619
//! `http://<host>:<port>/api/v1/<context>?token=<token>`.
720
//!
@@ -21,6 +34,12 @@
2134
//! All the tokes have the same permissions, so it is not possible to have
2235
//! different permissions for different tokens. The label is only used to
2336
//! identify the token.
37+
//!
38+
//! NOTICE: The token is not encrypted, so it is recommended to use HTTPS to
39+
//! protect the token from being intercepted.
40+
//!
41+
//! NOTICE: If both the `Authorization` header and the `token` GET param are
42+
//! provided, the `Authorization` header will be used.
2443
use std::sync::Arc;
2544

2645
use axum::extract::{self};
@@ -43,16 +62,26 @@ pub struct State {
4362
pub access_tokens: Arc<AccessTokens>,
4463
}
4564

46-
/// Middleware for authentication using a "token" GET param.
65+
/// Middleware for authentication.
66+
///
4767
/// The token must be one of the tokens in the tracker [HTTP API configuration](torrust_tracker_configuration::HttpApi).
4868
pub async fn auth(
4969
extract::State(state): extract::State<State>,
5070
extract::Query(params): extract::Query<QueryParams>,
5171
request: Request<axum::body::Body>,
5272
next: Next,
5373
) -> Response {
54-
let Some(token) = params.token else {
55-
return AuthError::Unauthorized.into_response();
74+
let token_from_header = extract_token_from_header(&request);
75+
76+
let token_from_get_param = params.token.clone();
77+
78+
let provided_tokens = (token_from_header, token_from_get_param);
79+
80+
let token = match provided_tokens {
81+
(Some(token_from_header), Some(_token_from_get_param)) => token_from_header,
82+
(Some(token_from_header), None) => token_from_header,
83+
(None, Some(token_from_get_param)) => token_from_get_param,
84+
(None, None) => return AuthError::Unauthorized.into_response(),
5685
};
5786

5887
if !authenticate(&token, &state.access_tokens) {
@@ -62,9 +91,21 @@ pub async fn auth(
6291
next.run(request).await
6392
}
6493

94+
fn extract_token_from_header(request: &Request<axum::body::Body>) -> Option<String> {
95+
let headers = request.headers();
96+
headers
97+
.get(axum::http::header::AUTHORIZATION)
98+
.and_then(|header_value| header_value.to_str().ok())
99+
.and_then(|header_str| {
100+
// Check if the header starts with "Bearer " and extract the token
101+
header_str.strip_prefix("Bearer ").map(std::string::ToString::to_string)
102+
})
103+
}
104+
65105
enum AuthError {
66106
/// Missing token for authentication.
67107
Unauthorized,
108+
68109
/// Token was provided but it is not valid.
69110
TokenNotValid,
70111
}

0 commit comments

Comments
 (0)