I am using Spring Boot 2.5.3 with Spring Security 5.5.1 for my web app and have setup authentication via OAuth2 with Google next to native users that authenticated with email and password. I extended the OidcUserService to save OAuth-users with their name and email in a database. My loadUser(OidcUserRequest userRequest)-method looks like this:
public class CustomOidcUserService extends OidcUserService {
............
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser user = super.loadUser(userRequest);
OidcUserInfo userInfo = user.getUserInfo();
if (!userAlreadyExists(userInfo.getGivenName(), userInfo.getEmail())) {
persistOAuthUser(userInfo);
}
return user;
}
..........
}
Now the line with if-statement throws a NullPointer because the UserInfo is null. I did some digging and found that the UserInfo is not retreived because in the OidcUserService in Line 177 the expression CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes) is false.
Google returns exactly these scopes in the OAuth2AccessTokenResponse (call made in DefaultAuthorizationCodeTokenResponseClient):
- "https://www.googleapis.com/auth/userinfo.profile"
- "https://www.googleapis.com/auth/userinfo.email"
- "openid"
While the configured accessibleScopes contain only "profile" and "email", without the host and so on so there is no match and the UserInfo is not retreived.
So now of course I can work around this and add these scopes to my UserService and then my UserInfo is set as expected. But that seems weird to me. Did Google change something here? Or am I still missing something?
My application.yml config also has nothing special regarding oauth:
security:
oauth2:
client:
registration:
google:
client-id: clientId
client-secret: clientSecret
I hope posting here was correct.... ☺️
Comment From: marcusdacoregio
Hi @hedgehog1833, thanks for the report.
Configuring the accessible scopes is the best option as per #6886.
I'm closing this but feel free to write here if you think that should be different, so we may have a discussion with @jgrandja.
Comment From: hedgehog1833
Hi @marcusdacoregio, thanks for your reply! What still confuses me is that in contrast to the problem in #6886 I do have the default scopes enabled (as I have the whole configuration of my flow on defaults). The requested scopes contain "profile" and "email". And Google answers with different scopes, these are taken over and then checked against the defaults, which then don't match. I don't understand how this currently can work for anybody without configuring these other scopes 🤔
Comment From: jgrandja
@hedgehog1833
Did Google change something here?
Yes they did. When OidcUserService was implemented, Google returned profile and email in the Access Token Response if profile and email is requested in the Authorization Request.
However, Google now returns https://www.googleapis.com/auth/userinfo.profile and https://www.googleapis.com/auth/userinfo.email in the Access Token Response even though profile and email is requested in the Authorization Request, which results in the UserInfo not being called. This is not consistent behaviour as per spec.
To work around this behaviour, you can configure OidcUserService.setAccessibleScopes() to force shouldRetrieveUserInfo() to return true.
Comment From: hedgehog1833
Thanks for the clarification! 🙏