From 8c1bbf3d1a0e6163a99f7dcc17b45d3de8151d19 Mon Sep 17 00:00:00 2001 From: Michel-Marie MAUDET Date: Thu, 6 Aug 2026 19:01:25 +0200 Subject: [PATCH] fix(oauth): Reject device code requests from unauthorised clients The device authorization endpoint issued a device code to any registered client, without checking that the client had registered the `urn:ietf:params:oauth:grant-type:device_code` grant type. Such a client could therefore start a device authorization flow and have the server show an approval prompt to a user, even though the subsequent token request was always going to be refused. RFC 8628 section 3.2 states that, in the event of an error such as an invalidly configured client, the device authorization endpoint responds in the same way as the token endpoint specified in RFC 6749 section 5.2, which defines `unauthorized_client` as "the authenticated client is not authorized to use this authorization grant type". Apply the same grant type check the token endpoint already performs, and add the `unauthorized_client` error code it requires. --- changelog.d/2110.bugfix.md | 1 + src/service/oauth/grant.rs | 6 ++++++ src/service/oauth/mod.rs | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelog.d/2110.bugfix.md diff --git a/changelog.d/2110.bugfix.md b/changelog.d/2110.bugfix.md new file mode 100644 index 000000000..030d55ab0 --- /dev/null +++ b/changelog.d/2110.bugfix.md @@ -0,0 +1 @@ +The OAuth 2.0 device authorization endpoint now rejects clients which did not register the device code grant type, instead of issuing them a device code. Contributed by @mmaudet. diff --git a/src/service/oauth/grant.rs b/src/service/oauth/grant.rs index d0bd66d0b..e99ae35e3 100644 --- a/src/service/oauth/grant.rs +++ b/src/service/oauth/grant.rs @@ -178,6 +178,11 @@ impl OAuthError { pub const fn invalid_grant(error_description: &'static str) -> Self { Self::new_static(ErrorCode::InvalidGrant, error_description) } + + #[must_use] + pub const fn unauthorized_client(error_description: &'static str) -> Self { + Self::new_static(ErrorCode::UnauthorizedClient, error_description) + } } impl Display for OAuthError { @@ -197,6 +202,7 @@ pub enum ErrorCode { InvalidGrant, InvalidClient, InvalidClientMetadata, + UnauthorizedClient, AuthorizationPending, ExpiredToken, } diff --git a/src/service/oauth/mod.rs b/src/service/oauth/mod.rs index 06f455fba..114b1edec 100644 --- a/src/service/oauth/mod.rs +++ b/src/service/oauth/mod.rs @@ -20,7 +20,7 @@ use url::Url; use crate::{ Dep, config, oauth::{ - client_metadata::{ApplicationType, ClientMetadata, ResponseType}, + client_metadata::{ApplicationType, ClientMetadata, GrantType, ResponseType}, grant::{ AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod, DeviceCodeRequest, DeviceCodeResponse, ErrorCode, OAuthError, ResponseMode, Scope, @@ -329,6 +329,10 @@ impl Service { return Err(OAuthError::new_static(ErrorCode::InvalidClient, "Invalid client ID")); }; + if !client_metadata.grant_types.contains(&GrantType::DeviceCode) { + return Err(OAuthError::unauthorized_client("Client cannot request this grant type")); + } + let requested_scopes = query .scope .to_scopes()