I am developing a browser extension that needs to authenticate with a Java Spring based api. The api needs to be able to identify users.
The extension acts as an SPA client and retrieves access and id tokens from the okta identity provider. The api accepts requests that are signed with these access tokens.
However, I’ve encountered some confusing behaviour that has left me wondering exactly what the api should use to uniquely identify users (in database records, etc).
The sub claim is different for each token. In the access token, it is the users email address. In the id token, it’s a random looking string of characters.
The api rejects requests that are signed with the identity token. Only the access tokens work.
If requests are signed with the access token, then the code from Okta’s tutorials for identifying the request caller doesn’t work. If I include a parameter @AuthenticationPrincipal OidcUser user it is always null.
I can get an identifier for the request caller by including a Authentication auth parameter and calling auth.getName(). It returns the callers email address (presumably by retrieving it from the sub claim of the access token?)
Can I trust this email address to uniquely identify each user, forever? I generally avoid using emails as unique identifiers in case users want to change them =/
A couple of things I want to cover about OpenID Connect and OAuth which I think will be useful without getting into the nitty gritty of Spring Security/our Spring Boot Starter.
The sub claim in the ID token is the users unique ID in Okta, this value will NOT change if the user updates their email address in Okta, but obviously a new one would be created if a new Okta account is created
If you want info about the user in the ID token, you should use the profile scope (to get things like their first and last name) and the email scope, to get their email address. More about ID Token claims here: OpenID Connect & OAuth 2.0 API | Okta Developer
The ID and Access Tokens serve two separate purposes. The ID token will contain information about the user, to enrich the front-end experience, and is used for proof that the user authenticated with the authorization server. The Access Token, as its name would imply, indicates what level of access this user has. The access token is always the one that should be sent to an external resource server as it is the one that support OAuth (authorization) use cases. Note that ID tokens cannot be revoked. I like the summary over here for reference: ID Tokens vs Access Tokens
Hi Andrea, thanks for the informative response, but I have to admit I am still a bit confused.
You mention the sub claim in the ID token will not change and can be trusted, but what about the sub claim in the access token? Will this ever change?
If it is only the sub claim in the ID token that I can trust, what is best practice for passing it along to a backend that uses your Spring Boot Starter?
The sub in the access token is typically the application username (coming from the application user profile), but with a custom authorization server (such as the one called default) you can modify this claim to suit your purposes if you want a different value in the sub claim. So with the default claim expression, it is possible that the value could change if the user’s application profile changes (which could be caused by their email with Okta changing).
I should mention that while the ID token stores the user’s Okta ID in the sub claim you will ALSO find this value in the uid claim within the Access token, so you don’t have to do anything special to get this value in that token instead. Details about the Access Token payload here: OpenID Connect & OAuth 2.0 API | Okta Developer
Am I correct that the value of auth.getName() in spring is extracted from the sub claim? So if I change this in the auth server, it will change in my API?
Is there a clean way to get the access token’s uid claim from within a spring controller method? I’d need this for almost every request, and it seems hacky to retrieve the auth header and decode the token every time.