Closed gburboz closed 5 years ago
@gburboz
Currently OAuth client provider user-info-uri config is mandatory and this HTTP call is always invoked even though in some cases we already have necessary info already available
The user-info-uri
is required for standard OAuth 2.0 Providers (implemented by DefaultOAuth2UserService
) but it's not mandatory for OIDC Providers (implemented by OidcUserService
).
If you look at the logic in OidcUserService.shouldRetrieveUserInfo()
you will see that the UserInfo
Endpoint is called if ALL these conditions are met:
user-info-uri
!= null
grant_type
== authorization_code
accessToken.scopes
contains any of profile
, email
, address
, phone
Otherwise the UserInfo
Endpoint is not called.
Does this help?
I am not sure why would there be any restriction wrt. scope profile
, email
, address
, phone
for calling this service.
Seems it is made mandatory for DefaultOAuth2UserService.loadUser(...)
which is what I see is being called.
Sample project that I am working is gb-oauth2-springboot-talk/Step-04-CustomProviderLogin where by for demo purpose I am configuring custom OAuth provider
My apologies but not sure how or when spring security uses OIDC vs plain OAuth for authentication.
@gburboz With regard to your comment
... not sure how or when spring security uses OIDC vs plain OAuth for authentication.
OpenID Connect authentication is triggered when the openid
scope is included in the Authenticaton request. In this case OidcUserService
is used and the UserInfo Endpoint might be called depending on the config of the ClientRegistration
- see OidcUserService.shouldRetrieveUserInfo()
.
If the openid
scope is NOT included in the Authentication Request than DefaultOAuth2UserService
is used and the UserInfo Endpoint must be called in order to obtain UserInfo - given that an ID Token is not available in this flow.
I hope this explains things? I'm going to close this issue as this works as expected.
@jgrandja , it does not work as expected by OIDC spec. Current spring-security design/implementation mandates invocation of user-info service by the client while no such restriction is imposed by spec. In certain scenarios like Google OIDC, same info is already available in id_token
hence HTTP call to user-info service can be avoided.
@gburboz
Current spring-security design/implementation mandates invocation of user-info service by the client while no such restriction is imposed by spec
This is not correct. Have you reviewed the logic in OidcUserService.shouldRetrieveUserInfo()
as mentioned in previous comment?
Given this Google client registration:
spring:
security:
oauth2:
client:
registration:
google:
client-id: client-id
client-secret: secret
scope: openid
The UserInfo endpoint will not be called.
And given this Google client registration:
spring:
security:
oauth2:
client:
registration:
google:
client-id: client-id
client-secret: secret
provider:
google:
user-info-uri:
The UserInfo endpoint will not be called because the user-info-uri
is empty.
Note: CommonOAuth2Provider.GOOGLE
provides defaults so you need to override here if you don't want the UserInfo endpoint called.
If you are still having issues than please put together a sample that reproduces this and I'll take a look.
I tried with spring-boot-starter-parent
version 2.0.6.RELEASE
, 2.1.0.RELEASE
and 2.1.1.BUILD-SNAPSHOT
with spring-security-oauth2-client
and spring-security-oauth2-jose
dependencies included.
Used below to configure Google provider without user-info-uri
spring.security.oauth2.client:
registration:
gbgoogle:
client-name: Custom Provider Google
client-id: your-google-client-id-here
client-secret: your-google-client-secret-here
authorization-grant-type: authorization_code
client-authentication-method: post
redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid profile email
provider:
gbgoogle:
user-name-attribute: sub
authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
token-uri: https://www.googleapis.com/oauth2/v4/token
user-info-uri:
jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
Considering scope has openid
, user-info should not have been mandatory but I get following error on UI
Your login attempt was not successful, try again.
Reason: [missing_user_info_uri] Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: gbgoogle
Following is part of exception stack trace with spring-boot-starter-parent
version 2.0.6.RELEASE
00:54:50 DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter : Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [missing_user_info_uri] Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: gbgoogle
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [missing_user_info_uri] Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: gbgoogle
at org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService.loadUser(DefaultOAuth2UserService.java:65)
at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:128)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:166)
@gburboz Based on the stacktrace, OAuth2LoginAuthenticationProvider
calls DefaultOAuth2UserService
which is the standard OAuth 2.0 Authorization Code flow. This is not the OpenID Connect flow. The reason is because the scope
you have configured is not comma delimited so the Set
of ClientRegistration.scopes
is actually one element of "openid profile email"
. Make sure you configure as scope: openid, profile, email
. This will ensure that OidcAuthorizationCodeAuthenticationProvider
is called instead which in turn will call OidcUserService
to perform the OpenID Connect flow.
Thanks @jgrandja , you are right about scope and after making changes you recommended it worked. I used space delimiter as that is what is mentioned in spec and it kind of worked with OP as it got what it expected but caused issues with spring-oauth.
May be we should update scope as list to eliminate the confusion and/or do not allow space char which is special for OAuth scope. This will help eliminate hard to detect bugs like this where by OAuth is used instead of OIDC even though scope openid
is specified.
@gburboz This is not an issue directly related to Spring Security. Spring Boot reads these properties via it's YAML reader. It's up to the user to ensure they have a properly configured/formatted yaml properties.
May be we should update scope as list to eliminate the confusion and/or do not allow space char which is special for OAuth scope
scope
should be Set
as duplicates should be avoided.
@jgrandja Is this still true with Spring Security in Spring Boot 2.1.6?
If you look at the logic in
OidcUserService.shouldRetrieveUserInfo()
you will see that theUserInfo
Endpoint is called if ALL these conditions are met:
user-info-uri
!=null
grant_type
==authorization_code
accessToken.scopes
contains any ofprofile
,address
,phone
Otherwise the
UserInfo
Endpoint is not called.
The reason I ask is that I'm having an issue getting all the user's attributes when my access token contains the following for scopes.
"scp": [
"openid",
"profile"
],
When I have the following, it works (but the access token also has most of the user's attributes in it):
"scope": "openid jhipster email offline_access profile",
I'm using OIDC discovery and just defining an issuer-uri
.
@jgrandja I think I can answer my own question. The UserInfo
Endpoint is called when using oauth2Login()
and issuer-uri
. It's not called when using oauth2ResourceServer()
. For now, I'll just add claims to my access token to resolve user info. I would like to know if it's possible to get user info when using oauth2ResourceServer()
though. It seems like functionality that should be available.
@mraible The logic you outlined for OidcUserService.shouldRetrieveUserInfo()
is correct. However, I just added an enhancement to this logic. Please see this comment for more info.
The UserInfo Endpoint is called only during an oauth2Login()
flow. It is never called from a oauth2ResourceServer()
since that flow is not related to OpenID Connect (oauth2Login()
). However, nothing is stopping you to make a call to the UserInfo Endpoint using the access token received from the oauth2ResourceServer()
, although this would be custom logic you would need to implement. If you decide to implement this logic, the claims returned from the UserInfo Endpoint cannot be added/enhanced to the access token since only the provider is allowed to create/sign an access token with all the necessary claims associated to it.
@jgrandja I am getting issue for Spring OAuth2 - when assigning the value to user-name-attribute; it gives the error “Missing attribute 'name' in attributes”
This is an old thread but as I had the same question, perhaps it can help someone else. with our keykloack configuration, all requested information was already in the id token, so I did not want spring security to call the user info endpoint.
As explained in this discussion, the solution is to have the user-info-uri property empty.
Do not use spring.security.oauth2.provider.[provider].issuer-uri property as it will automatically retrieve all provider info including the user info uri.
instead provide : authorization-uri, token-uri and jwk-set-uri with this setup, spring security will not have the user-info-uri and will not call the user info endpoint. having a dedicated property to drive spring behaviour would have be more straight forward (with no need to understand spring security behavior) but it's fine as we have at at the end the possibility to configure what we want.
How can my application but then get an access? I have Spring Cloud API Gateway setup as a oauth2 client. And I now understand, that it has to have openid scope so it can call the user-info endpoint. But I need to get access tokens, which I can then pass downstream to my resource server. The token provided from Microsoft will just be for user-info (graph) endpoint but not for my custom APIs
Summary
Open ID Connect Core 1.0 specification does not mandate invocation of UserInfo Endpoint and set of Standard Claims can be returned in either ID Token and/or Access Token as JWT
@jgrandja please review this issue which came out based off discussion with @jzheaux on issue #5629
Actual Behavior
Currently OAuth client provider
user-info-uri
config is mandatory and this HTTP call is always invoked even though in some cases we already have necessary info already available.Expected Behavior
When OAuth client provider
user-info-uri
is not provided andopenid
scope is mentioned (OP is OIDC), then get the claims from ID Token (which is always JWT) and additionally from Access Token if that is also a JWT.Alternatively have an additional config parameter to drive this behavior so that it can also be used with plain OAuth without OIDC as we already drive user identity with
user-name-attribute
and rest of the claims can just be considered additional info.e.g.: In case of Google Open ID Connect we can obtain user information from the Google's OIDC - ID token without having to invoke an additional UserInfo endpoint.
Version
Spring Security 5