Summary
Hi folks, can we have a discussion about the /userinfo endpoint? In short, authenticating a user via the /userinfo endpoint should not be used as a method of authentication. The OpenID Connect specification requires all responses from the Token Endpoint to contain an ID Token:
In addition to the response parameters specified by OAuth 2.0, the following parameters MUST be included in the response:
id_token ID Token value associated with the authenticated session.
Spring Security OAuth2 Client allows authentication with only an access token which leads to misuse of OAuth2 as authentication mechanism. OpenID Connect was specifically designed to mitigate this kind of security flaw. Spring security should not promote or enable known bad security practices. Please don't let it be the case going forward. If users need this functionality, let them use the old Oauth2 client. Or maybe it's time to rethink trying to mix metaphors and make OpenID Connect its own distinct project independent of OAuth2.
The Spring Security Documentation states the the Oauth2 Login is implemented according to the OpenID Connect 1.0 specification (highlighted in red boxes):
This is demonstrably false as the OpenID Connect spec indicates the sub Claim in the UserInfo Endpoint Response MUST be verified against the sub Claim in the ID Token per Section 5.3.2 of the spec:
The sub (subject) Claim MUST always be returned in the UserInfo Response.
NOTE: Due to the possibility of token substitution attacks (see Section 16.11), the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token. The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used.
No such validation is performed on the sub Claim currently.
On a personal integrity note, this has caused a bit of strife in my professional life. I am part of a project that is supposed to integrate with a "new shiny authentication service", but they are using this old and insecure OAuth2 flow to exchange an Access Token for UserInfo. My team has tried to appeal to them that what they are doing is insecure, but the Spring Security project is viewed by them as an authority on the topic (which is warranted). And since you allow it, it is not even given a second thought that it may be an insecure solution to Authentication. It's time to shut OAuth2 Authentication down. If not, these flaws will continue to propagate in the wild.
Actual Behavior
- Perform Authentication flow (e.g. Authorization code flow).
- Receive a response that contains an Access Token, but NO ID Token.
- ID token is NOT validated or required.
- Request for UserInfo is made to the /userinfo endpoint with Access Token obtained in step 2.
- User is authenticated and a Principal is created without verifying the
subClaim in the ID Token and thesubClaim in the UserInfo Response match exactly.
Expected Behavior
- Perform Authentication flow (e.g. Authorization code flow).
- Receive a response that contains an Access Token, but NO ID Token.
- ID token IS validated AND required.
- The flow should produce an error due to missing ID Token.
Or...
- Perform Authentication flow (e.g. Authorization code flow).
- Receive a response that contains an Access Token and an ID Token.
- ID token IS validated AND required.
- Request for UserInfo is made to the /userinfo endpoint with Access Token obtained in step 2.
- The
subClaim in the UserInfo Response MUST be verified to exactly match thesubClaim in the ID Token. 5a. If they do not match, the UserInfo Response values MUST NOT be used, and an error should be produced. 5b. If they do match, you can rely on the info as supplementary user information, but should not be used as a primary authentication mechanism because that was performed in step 3.
Configuration
See Sample
Version
All versions affected.
Sample
I'm only doing what is the "normal" UserInfo Authentication Flow allowed by the Oauth2 project, but this should not be allowed if trying to implement OpenID Connect. Anyhow, my code is available here: https://github.com/cpore/openidconnect-client-authentication
Comment From: jgrandja
@cpore If you believe there is a Security Vulnerability then this is not the proper channel for it. When you clicked on "New Issue", you are presented with 4 options, one of them being "Report a security vulnerability". Please use this channel going forward.
authenticating a user via the /userinfo endpoint should not be used as a method of authentication
This is not the current behaviour. The UserInfo Endpoint is actually OPTIONAL and may not even be called in certain authn flows, depending how the client and/or application is configured. If it is called, the Bearer Access Token obtained during authentication is passed to it to determine which claims of the authenticated user to return. NOTE: Authentication succeeds before this step, when the client receives the id_token from the Token Endpoint and verifies the id_token. After authentication is successful, the UserInfo Endpoint MAY be called.
Spring Security OAuth2 Client allows authentication with only an access token which leads to misuse of OAuth2 as authentication mechanism
Again, this is not the current behaviour. The sample you provided is quite minimal and doesn't demonstrate this.
This is demonstrably false as the OpenID Connect spec indicates the sub Claim in the UserInfo Endpoint Response MUST be verified against the sub Claim in the ID Token per Section 5.3.2 of the spec:
No such validation is performed on the sub Claim currently.
Again, this is not correct. The validation is enforced in OidcUserService
Expected Behavior
Or...
- Perform Authentication flow (e.g. Authorization code flow).
- Receive a response that contains an Access Token and an ID Token.
- ID token IS validated AND required.
- Request for UserInfo is made to the /userinfo endpoint with Access Token obtained in step 2.
- The
subClaim in the UserInfo Response MUST be verified to exactly match thesubClaim in the ID Token. 5a. If they do not match, the UserInfo Response values MUST NOT be used, and an error should be produced. 5b. If they do match, you can rely on the info as supplementary user information, but should not be used as a primary authentication mechanism because that was performed in step 3.
This is exactly the current behaviour as per spec. I guess I'm quite confused as your findings do not align with the current implementation. I would suggest that you try the OAuth 2.0 Login samples and you will see that the flow is implemented as per "Expected Behaviour" above. Try either Spring Authorization Server, Google or Okta as those implement OpenID Connect.
I'm closing this as invalid. However, if you disagree, please provide a minimal sample with detailed steps on how to reproduce spec non compliance.
Comment From: rozagerardo
Hey @jgrandja can we revisit this issue and clarify a few points?
I now prepared a functional sample project to point out the scenario that I believe @cpore was referring to:
https://github.com/rozagerardo/samples/tree/spring-security-with-keycloak-AS
There are three services over there to mimic a standard OAuth 2 configuration:
- A Keycloak Authorization Server
- A Spring Security OAuth 2 Client
- A Spring Security Resource Server
In the Client we don't use the openid scope, just as in the @cpore sample:
https://github.com/rozagerardo/samples/blob/spring-security-with-keycloak-AS/spring-client/src/main/resources/application.yml#L9
And we're using the .oauth2Login(...) setup (that is, not the .oauth2Client():
https://github.com/rozagerardo/samples/blob/spring-security-with-keycloak-AS/spring-client/src/main/java/com/rozagerardosamples/spring/ClientSecurityConfig.java#L20
If you try out this configuration, you'll see that we can Log in with this setup (instructions on the README file), and that is done via the UserInfo endpoint.
Debugging the process:
1- the OAuth2LoginAuthenticationProvider.authenticate is invoked. This comes into play when the openid scope is not provided, as you can see here:
2- This calls DefaultOAuth2UserService.loadUser, and we can see from starters that this uses the UserInfo endpoint to get the user data:
3- We can then later on confirm that the endpoint actually getting called for this case:
4- The flow finishes successfully, I am logged in and able to access the secured resources too of course.
Note: this is working because I am using an old(ish) Keycloak version, since v 20 they don't allow access to the UserInfo endpoint if the openid scope is not present:
https://github.com/keycloak/keycloak/issues/14184
But anyway, it still evidencing that we're allowing this on the Spring Security Client side. I haven't analyzed this in depth to figure out if it is actually a security vulnerability or even a bad practice...after all, it corresponds to the AS to check this; we can't be responsible for their functionality. But it's true we're encouraging at some level this practice as well.
Let me know if I am missing something here, of course.
BTW, I am writing an lesson on this, so it's certainly helpful to know your position towards this and what we can expect of the OAuth2 Client functionality in the future :)
Comment From: sjohnr
@rozagerardo thanks for reaching out. However, as mentioned above
If you believe there is a Security Vulnerability then this is not the proper channel for it. When you clicked on "New Issue", you are presented with 4 options, one of them being "Report a security vulnerability". Please use this channel going forward.
discussing security issues with currently supported versions of the framework in the open is not appropriate. Please see the Security Policy which is also clearly linked from the main page of this project.
If you try out this configuration, you'll see that we can Log in with this setup (instructions on the README file), and that is done via the UserInfo endpoint.
1- the
OAuth2LoginAuthenticationProvider.authenticateis invoked. This comes into play when theopenidscope is not provided, as you can see here:
The functionality you're describing (when openid scope is not present) is the "Login with GitHub" scenario described in the overview of OAuth 2.0 Login in the reference. GitHub is an example of a provider that does not implement OpenID Connect 1.0.
If you believe you have found a vulnerability and can provide details, please either report this directly to GitHub (or whatever provider you are using) or if the issue is with OAuth2 Client in Spring Security, by following our Security Policy.
I haven't analyzed this in depth to figure out if it is actually a security vulnerability or even a bad practice
As mentioned above, please do not disclose such findings here. Report them responsibly by following the above linked security policy. I'm going to lock this discussion as it does not seem productive to allow responses that would continue this discussion in public.