Skip to content

Query parameters in authorization-url are double-encoded #7871

Closed
@manuelbl

Description

@manuelbl

Summary

If the autorization URL of a OAuth 2.0 provider contains query parameters with URL encoded characters, they are encoded again and the request is rejected by the IdP.

Example: If the authorization URL in application.yml is configured as:

authorization-uri: 'https://iam.abc.com/idp/oauth2/authorize?claims=%7B%22urn%3Aabc%3Aplace_of_birth%22%3Anull%7D

Then the resulting URL becomes (somewhat shortened):

https://iam.abc.com/idp/oauth2/authorize?acr_values=quo1&claims=%257B%2522urn%253Aabc%253Aplace_of_birth%2522%253Anull%257D&response_type=code&client_id=c1&scope=openid%20profile%20email&state=83l9MUgoab0w&redirect_uri=http://localhost:8081/login/oauth2/code/abc&nonce=w4VgoHfH9f71'

The claims parameter is now double encoded. The IdP doesn't understand it anymore and rejects the authorization request.

Actual Behavior

The query parameter value is double encoded and no longer valid for the IdP.

Correct value in configuration:

claims=%7B%22urn%3Aabc%3Aplace_of_birth%22%3Anull%7D

Invalid value in request:

claims=claims=%257B%2522urn%253Aabc%253Aplace_of_birth%2522%253Anull%257D

Expected Behavior

No double encoding should be applied. The query parameter value should be retained without changes.

Correct value in configuration:

claims=%7B%22urn%3Aabc%3Aplace_of_birth%22%3Anull%7D

Correct value in request:

claims=%7B%22urn%3Aabc%3Aplace_of_birth%22%3Anull%7D

Notes

If an invalid URL without URL encoding is used::

claims={"urn:abc:place_of_birth":null}

the resulting URL is invalid as well (and rejected by the IdP):

claims={%22urn:abc:date_of_birth%22:null}

Furthermore, if URLs were to be entered without encoding the query parameters, many parameter values could not be entered as the URL would no longer be parseable.

I am aware of issue #5760 and the associated PR #6299. Unfortunately, it fixes the problem only partially.

At first look, it seems that the root cause is at OAuth2AuthorizationRequest.java, line 395 where UriComponentsBuilder.fromHttpUrl is called. This method is for parsing URL templates that contain substitutable components and has documented limitations regarding query parameters. Since substitutable components are not needed, a more suitable method should be used instead.

Configuration

application.yml

server:
  port: 8081

spring:
  security:
    oauth2:
      client:
        registration:
          abcfund:
            client-id: abc
            client-secret: 0123456789abcdef
            clientAuthenticationMethod: post
            provider: my-idp
            scope:
              - openid
              - profile
              - email
        provider:
          my-idp:
            issuer-uri: https://iam.abc.com/idp/oauth2
            authorization-uri: 'https://iam.abc.com/idp/oauth2/authorize?acr_values=quo1&claims=%7B%22urn%3Aabc%3Aplace_of_birth%22%3Anull%7D'

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.2.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

Version

  • Spring Security 5.2.1
  • Spring Boot 2.2.4

Sample

In addition to the above configurations, the following code completes a sample. There is no custom WebSecurityConfigurerAdapter. The out-of-the-box configuration sufficient.

DemoApplication.java

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

HomeController.java

package demo;

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class HomeController {

    @GetMapping("/")
    Map<String, Object> hello(Authentication authentication) {
        OAuth2AuthenticationToken token = (OAuth2AuthenticationToken)authentication;
        return token.getPrincipal().getAttributes();
    }

}

Metadata

Metadata

Assignees

Labels

in: oauth2An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)status: backportedAn issue that has been backported to maintenance branchestype: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions