When parsing OAuth2 access token response a nested JSON object causes the response parsing to fail.
When attempting to use Spring Security OAuth to allow logins against a provider that responds with objects in their access token reponse an error message is shown:
An error occurred reading the OAuth 2.0 Access Token Response: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (ByteArrayInputStream); line: 7, column: 14] (through reference chain: java.util.LinkedHashMap["object"]); nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (ByteArrayInputStream); line: 7, column: 14] (through reference chain: java.util.LinkedHashMap["object"])
According to the OAuth spec https://tools.ietf.org/html/rfc6749#section-5.1 clients must ignore values they don't understand. The value should either end up in the additionalParameters of the OAuth2AccessTokenResponse or it should be ignored.
Jackson is being used to parse the JSON response (seems to be default in my spring-boot application).
Spring Security 5.1.3, issue also looks to be present on master.
You can see a test case that currently fails in: https://github.com/spring-projects/spring-security/compare/master...buckett:oauth-response?expand=1
An example response that triggers this bug is:
{
"access_token": "access-token-1234",
"token_type": "bearer",
"expires_in": "3600",
"scope": "read write",
"refresh_token": "refresh-token-1234",
"object": {"param": "value" },
"custom_parameter": "custom-value"
}
To workaround this I copied OAuth2AccessTokenResponseHttpMessageConverter into my own project and updated the tokenResponseParameters to be a Map<String, Object>. Here's a gist of the file: https://gist.github.com/buckett/7fe338901b4fcfefb6cfce637629af3f
Then I just connect this converter up to the RestTemplate that's used by the OAuth 2 code:
```
.oauth2Login().tokenEndpoint().accessTokenResponseClient(accessTokenResposeClient());
[..snipped..]
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResposeClient() {
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
HttpClient requestFactory = HttpClientBuilder.create().build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(requestFactory));
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
client.setRestOperations(restTemplate);
return client;
}
````
This isn't the actual code I'm using as I've removed other changes that aren't needed so it may not work as expected.
@buckett The solution you implemented with providing your own AbstractHttpMessageConverter<OAuth2AccessTokenResponse> is exactly what you would do for these kind of custom token responses. This is the main extension point for RestTemplate, the HttpMessageConverter for request/response serializing/deserializing. OAuth2AccessTokenResponseHttpMessageConverter is simply a default implementation.
It's not clear to me if you're still having a problem or are you expecting a different way of implementing your setup?
As per spec:
The parameters are serialized into a JavaScript Object Notation (JSON)
structure by adding each parameter at the highest structure level.
Parameter names and string values are included as JSON strings.
Numerical values are included as JSON numbers. The order of
parameters does not matter and can vary.
Based on my understanding how the spec reads, each parameter must be at the highest (root) structure level and are either strings or numbers. However, your example token response has a JSON object value at the root level with the parameter names/values at the next level below the object. So therefore the value of the root level parameter is an object and not a string or number as the spec dictates.
Does this make sense?
When I read the spec I was reading that paragraph as applying to the previously specified parameters (access_token, token_type, expires_in, refresh_token, scope).
It also goes on to say that the "the client MUST ignore unrecognized values names in the response", which again suggests we should be a little more forgiving in how we deal with unexpected value names.
If nothing else it would have been helpful if this was easier to code around as this took me a little while to debug (I came across this upgrading from Spring 5 to Spring 5.1, Spring 5 was forgiving of extra values I think).
As per spec:
The client MUST ignore unrecognized value names in the response. The
sizes of tokens and other values received from the authorization
server are left undefined. The client should avoid making
assumptions about value sizes. The authorization server SHOULD
document the size of any value it issues.
I see your point based on the reference in bold.
If nothing else it would have been helpful if this was easier to code around
Our goal is to make it easy for the user so this is valuable feedback for us. Do you have a suggestion on improving? Are you possibly interested in submitting a PR for this improvement?
When will this be fixed?
@Gyurmatag We'll do our best to fix this soon.
when will this change be available? I checked my project maven dependencies and I am on spring-security-oauth2-core-5.2.1.RELEASE.jar. Once the change is live which version will have this change? thanks!
To workaround this I copied
OAuth2AccessTokenResponseHttpMessageConverterinto my own project and updated thetokenResponseParametersto be aMap<String, Object>. Here's a gist of the file: https://gist.github.com/buckett/7fe338901b4fcfefb6cfce637629af3fThen I just connect this converter up to the
RestTemplatethat's used by the OAuth 2 code:.oauth2Login().tokenEndpoint().accessTokenResponseClient(accessTokenResposeClient()); [..snipped..] private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResposeClient() { DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient(); RestTemplate restTemplate = new RestTemplate(Arrays.asList( new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); HttpClient requestFactory = HttpClientBuilder.create().build(); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(requestFactory)); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); client.setRestOperations(restTemplate); return client; }This isn't the actual code I'm using as I've removed other changes that aren't needed so it may not work as expected.
how are you able to cast "client" to OAuth2AccessTokenResponseClient? any help is greatly appreciated.
The change is in 5.3.0.BUILD-SNAPSHOT and will be in 5.3.0.RELEASE as indicated by the milestone selected with this issue.
The change is in 5.3.0.BUILD-SNAPSHOT and will be in 5.3.0.RELEASE as indicated by the milestone selected with this issue.
thank you for the update.
The change is in 5.3.0.BUILD-SNAPSHOT and will be in 5.3.0.RELEASE as indicated by the milestone selected with this issue.
I as was just wondering if there any options to reference to the snapshot version ? our app is live but broken for facebook login. I greatly appreciate your response.
Please see the relevant section of the reference documentation https://docs.spring.io/spring-security/site/docs/5.3.0.BUILD-SNAPSHOT/reference/html5/#getting
I upgraded to 5.3.0 for org.springframework.security.oauth2.core and still facing the issue. I am using facebook OAuth2 for user authentication.
org.springframework.http.converter.HttpMessageNotReadableException: An error occurred reading the OAuth 2.0 Error: Invalid JSON input: Cannot deserialize instance of java.lang.String out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (sun.net.www.protocol.http.HttpURLConnection$HttpInputStream); line: 1, column: 10] (through reference chain: java.util.LinkedHashMap["error"]); nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Invalid JSON input: Cannot deserialize instance of java.lang.String out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.lang.String out of START_OBJECT token
at [Source: (sun.net.www.protocol.http.HttpURLConnection$HttpInputStream); line: 1, column: 10] (through reference chain: java.util.LinkedHashMap["error"])
at org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter.readInternal(OAuth2ErrorHttpMessageConverter.java:78) ~[spring-security-oauth2-core-5.3.0.RELEASE.jar!/:5.3.0.RELEASE]
@jscoder1009 This ticket is related to a parsing error for a _Successful Token Response_. However, based on the stacktrace, the issue you are having happens during an attempt to parse an _Error Token Response_.
org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter.readInternal(OAuth2ErrorHttpMessageConverter.java:78)
It looks like Facebook does not return a correctly formatted OAuth 2.0 Error as defined in 5.2. Error Response.
You can override the default OAuth2ErrorResponseErrorHandler assigned to RestTemplate.setErrorHandler() in the associated OAuth2AccessTokenResponseClient (eg. DefaultAuthorizationCodeTokenResponseClient) to work around this issue with Facebook. Please see the ref doc for further details on custom configuration.
I landed here after a very long excursion! I wanted to report that Strava is also returning an object in one of their response attributes. Upgrading to 5.3.0 resolved the issue for me (was on 5.2.x).
https://developers.strava.com/docs/authentication/
Example Response
{
"token_type": "Bearer",
"expires_at": 1568775134,
"expires_in": 21600,
"refresh_token": "e5n567567...",
"access_token": "a4b945687g...",
"athlete": {
#{summary athlete representation}
}
}
Thanks everyone for their hard work!
spring:
security:
oauth2:
client:
registration:
facebook:
client-id: xxddd...
clientSecret: secrettt....
redirect-uri: https://example.com/login/oauth2/code/facebook
Issue was redirect-uri was going as http. I explicitly added callback URL with https and this fixed my issue.
@jscoder1009 where did you add callback URL with https?
Most helpful comment
The change is in 5.3.0.BUILD-SNAPSHOT and will be in 5.3.0.RELEASE as indicated by the milestone selected with this issue.