Description
Describe the bug
Spring authorization server returns error response 'invalid_request ' when requesting a token with grant type "authorization_code" (with PKCE) from a client which is configured public (no client_secret, client-authentication-method "none").
To Reproduce
Started with a clean spring boot project for a local try-out of spring boot authorization server.
Configured two clients:
- a confidential client following the config from https://docs.spring.io/spring-authorization-server/reference/getting-started.html
- a public client following the instructions from https://docs.spring.io/spring-authorization-server/reference/guides/how-to-pkce.html
Via Postman I tried to issue a token from each client using grant_type "authorization_code" (with PKCE). Same user credentials were used in both cases, requested same /oauth2/authorize
and /oauth2/token
endpoints.
Both requests get a code returned from /oauth2/authorize
endpoint & try to exchange it for a token at /oauth2/token
sending grant_type (authorization_code), code, redirect_uri and code_verifier. The request for the confidential client succeeds, while the request for public client gets returned {"error": "invalid_request"}
.
Error log from spring authorization server:
2024-11-11T22:47:04.407+01:00 TRACE 79294 --- [identityprovider] [nio-9000-exec-3] o.s.a.w.OAuth2ClientAuthenticationFilter : Client authentication failed: [invalid_request]
org.springframework.security.oauth2.core.OAuth2AuthenticationException: null
at org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter.convert(ClientSecretBasicAuthenticationConverter.java:78) ~[spring-security-oauth2-authorization-server-1.3.3.jar:1.3.3]
at org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter.convert(DelegatingAuthenticationConverter.java:60) ~[spring-security-oauth2-authorization-server-1.3.3.jar:1.3.3]
at org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter.doFilterInternal(OAuth2ClientAuthenticationFilter.java:134) ~[spring-security-oauth2-authorization-server-1.3.3.jar:1.3.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.14.jar:6.1.14]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.3.4.jar:6.3.4]
Expected behavior
Authorization server successfully issues a token for the public client.
Sample
Please see client config and requests below or in sample project
application.yml
server:
port: 9000
spring:
application:
name: spring-authorization-sample
security:
user:
name: user1
password: password
oauth2:
authorizationserver:
client:
confidential-client:
registration:
client-id: "confidential-client"
client-secret: "{noop}secret"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "refresh_token"
- "client_credentials"
redirect-uris:
- "http://127.0.0.1:8080/login/oauth2/code/confidential-client-oidc"
scopes:
- "openid"
- "profile"
- "message.read"
require-authorization-consent: true
public-client:
registration:
client-id: "public-client"
client-authentication-methods:
- "none"
authorization-grant-types:
- "authorization_code"
redirect-uris:
- "http://127.0.0.1:8080/login/oauth2/code/public-client-oidc"
scopes:
- "openid"
- "profile"
- "message.read"
require-authorization-consent: true
require-proof-key: true
Postman collection
{
"info": {
"_postman_id": "8cb75b26-f6b7-4ddf-82bf-c5e6e9e0600f",
"name": "Spring Authorization Sample",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "29654337"
},
"item": [
{
"name": "Confidential client token request",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "grant_type",
"value": "authorization_code_with_pkce",
"type": "string"
},
{
"key": "password",
"value": "",
"type": "string"
},
{
"key": "username",
"value": "",
"type": "string"
},
{
"key": "scope",
"value": "message.read",
"type": "string"
},
{
"key": "clientSecret",
"value": "secret",
"type": "string"
},
{
"key": "redirect_uri",
"value": "http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc",
"type": "string"
},
{
"key": "clientId",
"value": "messaging-client",
"type": "string"
},
{
"key": "authUrl",
"value": "http://localhost:9000/oauth2/authorize",
"type": "string"
},
{
"key": "accessTokenUrl",
"value": "http://localhost:9000/oauth2/token",
"type": "string"
},
{
"key": "tokenName",
"value": "spring auth",
"type": "string"
},
{
"key": "headerPrefix",
"value": "Bearer ",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": ""
}
},
"response": []
},
{
"name": "Public client token request",
"request": {
"auth": {
"type": "oauth2",
"oauth2": [
{
"key": "grant_type",
"value": "authorization_code_with_pkce",
"type": "string"
},
{
"key": "username",
"value": "",
"type": "string"
},
{
"key": "password",
"value": "",
"type": "string"
},
{
"key": "scope",
"value": "message.read",
"type": "string"
},
{
"key": "accessTokenUrl",
"value": "http://localhost:9000/oauth2/token",
"type": "string"
},
{
"key": "authUrl",
"value": "http://localhost:9000/oauth2/authorize",
"type": "string"
},
{
"key": "clientId",
"value": "public-client",
"type": "string"
},
{
"key": "code_verifier",
"value": "",
"type": "string"
},
{
"key": "challengeAlgorithm",
"value": "S256",
"type": "string"
},
{
"key": "clientSecret",
"value": "",
"type": "string"
},
{
"key": "redirect_uri",
"value": "http://127.0.0.1:8080/login/oauth2/code/public-client-oidc",
"type": "string"
},
{
"key": "tokenName",
"value": "spring auth",
"type": "string"
},
{
"key": "headerPrefix",
"value": "Bearer ",
"type": "string"
},
{
"key": "addTokenTo",
"value": "header",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": ""
}
},
"response": []
}
]
}