Skip to content

State‐Parameter‐Saga

Okke Harsta edited this page Jul 11, 2024 · 4 revisions

Encoding / decoding of the state query parameter

Introduction

We expect RelyingParties to URL-encode query parameters prior to sending them to the OIDC Authorization endpoint. The query parameters are decoded by the Spring Boot framework used by OIDC and are URL-encoded again in the redirect URL response back to RelyingParty.

Old situation (release 6.1.10)

In the 6.1.10 release prior to any changes in the decoding / encoding algorithm, the following behaviour was observed:

Raw parameter Parameter encoded by the RP Decoded parameter in OIDC-NG Encoded return parameter
https://example.com https%3A%2F%2Fexample.com https://example.com https://example.com

It appears the return parameter is not encoded, but this caused by the use of the Spring org.springframework.web.util.UriComponentsBuilder. The UriComponentsBuilder does not encode : and / characters. UriComponentsBuilder is encoding the URI in accordance with RFC 3986 (see , in particular section 3.4 which is about the 'query' component of a URI). Within the query component, the characters / and : are permitted, and do not need escaping.

UriComponentsBuilder.fromUriString("/").queryParam("state", "https://example.com?key=value").toUriString();
// returns /?state=https://example.com?key%3Dvalue

Note the URL-encoding of key=value, but not the https://example.com part.

This is however not very intuitive, as other URL encode / decode libraries do escape the / and : characters. See the following Node example:

$ node
> encodeURIComponent("https://example.com")
'https%3A%2F%2Fexample.com'

and Java default library example

jshell> URLEncoder.encode("https://example.com", Charset.defaultCharset());
$1 ==> "https%3A%2F%2Fexample.com"

The correct fix for State parameter with possible url encoding is decoded when the user is redirected to the RP would have been to NOT use the UriComponentsBuilder, but the default URLEncoder to encode the query parameters when constructing the redirect URL in the Authorization endpoint.

Mistakenly the fix applied, was to skip the encoding of the state parameter overall in this commit.

Resulting in this behaviour:

Test parameter Parameter encoded by RestAssured test framework Decoded parameter in OIDC-NG Return parameter (not encoded)
https%3A%2F%2Fexample.com https%253A%252F%252Fexample.com https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com

The fix looks ok, but was indeed not, as the RestAssured test framework encodes the query parameters also by default, resulting in double encoding, masking the bug. Re-running the integration test against this version of OICD-NG with single encoding reveales the error.

return given().redirects().follow(false)
            .urlEncodingEnabled(false)
            .when()
            .header("Content-type", "application/json")
            .queryParams(queryParams)
            .get("oidc/authorize");

Resulting in:

Test parameter Parameter NOT encoded by RestAssured test framework Decoded parameter in OIDC-NG Return parameter (not encoded)
https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https://example.com https://example.com

New situation (release 6.1.11)

The above results were deployed in version 6.1.11 and this broke the login flow of some Relying Parties, as they send the parameters encoded - which is correct - but got them back not encoded again (e.g. still decoded).

Newer situation (release 6.1.12)

The following fix was applied to ensure the state parameter was not decoded and also not encoded. See Do not decode / encode state parameter in redirectURI. This resulted in the following behaviour:

Test parameter Parameter NOT encoded by RestAssured test framework Parameter not decoded in OIDC-NG Return parameter (not encoded)
https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com

Although technically correct, this provided a challenge to existing Relying Parties who send the state parameter encoded, but actually already worked around the old previous issue that it came back still decoded (e.g. before release 6.1.11.)

Current situation (release 6.1.13)

To revert and fix the situation in release 6.1.12, we introduced a feature toggle described in Undo state parameter encoding implemented in this commit. Relying Parties can be configured with the option oidc:state_parameter_decoding_disabled. When this option is set to True then the behaviour of release 6.1.12 is applied, otherwise the behaviour of release 6.1.11 is applied (the default). However this did not solve the underlying problem of the 6.1.11 release, where the query parameters were not encoded when send back to the RP in the redirect URL response of the Authorization endpoint:

state_parameter_decoding_disabled Test parameter Parameter NOT encoded by RestAssured test framework Parameter in OIDC-NG Return parameter
False (default) https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https://example.com https://example.com
True https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com

Future situation (release 6.1.14)

The actual correct implementation is the old situation in release 6.1.10, but with the correct bug-fix applied. The Spring boot framework used in OIDC-NG decodes all query parameters and subsequently we use the default Java URL-encoding algorithm to encode the parameters before we redirect to the Relying Party

state_parameter_decoding_disabled Test parameter Parameter NOT encoded by RestAssured test framework Parameter in OIDC-NG Return parameter standard encoding
Not Used https%3A%2F%2Fexample.com https%3A%2F%2Fexample.com https://example.com https%3A%2F%2Fexample.com

Disclaimer

If a Relying Party does not encode the query parameters and the request is accepted (e.g. it does not contain forbidden characters like {), then the return paramaters are still encoded. As we do expect the RelyingParty to properly decode them, this should not pose a problem, but do note that the input and output parameters are not equal then:

Test parameter Parameter NOT encoded by RestAssured test framework Parameter in OIDC-NG Return parameter standard encoding
https://example.com https://example.com https://example.com https%3A%2F%2Fexample.com

Best practices

If the state parameter is used for more then to prevent CSRF Attacks and contains information about the application state, then we advice an encoding that is idempotent for URL decoding / encoding. The Base64URL encoding (not to be confused with Base64 encoding) is then the preferred encoding.