commit 07f18ff7bf19595900a2921d47d6d57f1abdadf6
parent 7c2ebcc2b94552b74e4ae7283de68c6d6d774d97
Author: Henrique Chan Carvalho Machado <henriqueccmachado@tecnico.ulisboa.pt>
Date: Mon, 19 Jan 2026 18:31:35 +0100
Enforce redirect_uri binding in token exchange
Bind authorization codes to the original redirect_uri and validate it during
token exchange. The token endpoint now requires redirect_uri and rejects
mismatches or unexpected values, preventing authorization code interception per
RFC 6749, 4.1.3.
Diffstat:
3 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/kych_oauth2_gateway/src/db/authorization_codes.rs b/kych_oauth2_gateway/src/db/authorization_codes.rs
@@ -27,6 +27,7 @@ pub struct CodeExchangeData {
pub session_status: SessionStatus,
pub existing_token: Option<String>,
pub existing_token_expires_at: Option<DateTime<Utc>>,
+ pub redirect_uri: Option<String>,
}
/// Create a new authorization code for a session
@@ -85,7 +86,8 @@ pub async fn get_code_for_token_exchange(
vs.client_id,
vs.status AS session_status,
at.token AS existing_token,
- at.expires_at AS existing_token_expires_at
+ at.expires_at AS existing_token_expires_at,
+ vs.redirect_uri
FROM updated_code uc
JOIN code_data cd ON uc.id = cd.id
JOIN oauth2gw.verification_sessions vs ON vs.id = uc.session_id
@@ -107,6 +109,7 @@ pub async fn get_code_for_token_exchange(
session_status: row.get("session_status"),
existing_token: row.get("existing_token"),
existing_token_expires_at: row.get("existing_token_expires_at"),
+ redirect_uri: row.get("redirect_uri"),
}
}))
}
diff --git a/kych_oauth2_gateway/src/handlers.rs b/kych_oauth2_gateway/src/handlers.rs
@@ -545,6 +545,34 @@ pub async fn token(
));
}
+ // RFC 6749 Section 4.1.3: If redirect_uri was included in the authorization request,
+ // it MUST be present and match exactly in the token request
+ match &data.redirect_uri {
+ Some(stored_uri) if stored_uri != &request.redirect_uri => {
+ tracing::warn!(
+ "redirect_uri mismatch for code {}: expected '{}', got '{}'",
+ request.code,
+ stored_uri,
+ request.redirect_uri
+ );
+ return Err((
+ StatusCode::BAD_REQUEST,
+ Json(ErrorResponse::new("invalid_grant")),
+ ));
+ }
+ None => {
+ tracing::warn!(
+ "redirect_uri provided in token request but was not stored during authorization for code {}",
+ request.code
+ );
+ return Err((
+ StatusCode::BAD_REQUEST,
+ Json(ErrorResponse::new("invalid_grant")),
+ ));
+ }
+ Some(_) => {}
+ }
+
// Check for existing token
if let Some(existing_token) = data.existing_token {
tracing::info!(
diff --git a/kych_oauth2_gateway/src/models.rs b/kych_oauth2_gateway/src/models.rs
@@ -51,15 +51,13 @@ pub struct AuthorizeResponse {
pub state: String,
}
-// Token endpoint
-// WARING: RFC 6749 also requires:
-// - redirect_uri
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenRequest {
pub grant_type: String,
pub code: String,
pub client_id: String,
pub client_secret: String,
+ pub redirect_uri: String,
}
#[derive(Debug, Deserialize, Serialize)]