Getting "Invalid JOSE Header kid" error


#1

Background
I am modifying my existing Spring-secured API (not Spring-Boot) to work with bearer tokens that were issued to the client by Okta. Since every example I could find uses Spring-Boot, I tried getting it to work with the okta-spring-boot-starter artifact, but I’m stuck.

I have setup an Okta developer account and I am able to get a valid token by POSTing to https://<my dev subdomain>.oktapreview.com/oauth2/default/v1/token

I can validate the token by POSTing to https://<my dev subdomain>.oktapreview.com/oauth2/default/v1/introspect:

{
	"active": true,
	"scope": "customScope",
	"exp": 1541536634,
	"iat": 1541533034,
	"sub": "<my client id>",
	"aud": "api://default",
	"iss": "https://<my dev subdomain>.oktapreview.com/oauth2/default",
	"jti": "AT.OrvTA0dVK7eoN8upzz9U50C4kMsXMCXb5FZcQB5i4lI",
	"token_type": "Bearer",
	"client_id": "<my client id>"
}

I then use that token as a Bearer token in the Authorization header in a call to my protected resource.

My Code

@Configuration
@EnableResourceServer
public class OauthResourceConfig extends ResourceServerConfigurerAdapter {

	@Value("${okta.oauth2.clientId}")
	String resourceId;

	@Value("${okta.oauth2.baseUrl}/oauth2/v1/keys")
	String jwksUrl;

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http
				.httpBasic().disable()
				.requestMatchers().antMatchers("/fortellis/**").and()
				.authorizeRequests().antMatchers("/").permitAll().and()
				.csrf().disable();
	}

	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
		resources
				.tokenServices(tokenServices())
				.resourceId(resourceId);
	}

	@Primary
	@Bean
	public DefaultTokenServices tokenServices() throws Exception {
		DefaultTokenServices tokenServices = new DefaultTokenServices();
		tokenServices.setTokenStore(tokenStore());
		return tokenServices;
	}

	@Bean
	public TokenStore tokenStore() {
		return new JwkTokenStore(jwksUrl); //
	}
}

Here are the properties:

    // Okta Oauth2 Properties
    setProperty("okta.oauth2.baseUrl", "https://<my dev subdomain>.oktapreview.com");
    setProperty("okta.oauth2.issuer", "https://<my dev subdomain>.oktapreview.com/oauth2/default/v1");
    setProperty("okta.oauth2.clientId", "<my client id>");
    setProperty("okta.oauth2.audience", "api://default");
    setProperty("okta.oauth2.scopeClaim", "customScope");
    setProperty("okta.oauth2.rolesClaim", "groups");

The Problem
When I issue a request to my secured URL I get this error:

{
    "error": "invalid_token",
    "error_description": "Invalid JOSE Header kid (iAzphTQ0ksc7h4fwgVAW71-f4uRXZPOs0MEElBG6hhc)"
}

Any help getting me going down the right path would be much appreciated. I’m three days into this already. :frowning:


#2

Hey @JoeJErnst,

I’m not sure what issues you would run into outside of a Spring Boot environment, though I suspect this is related to that. Can you describe your setup in a bit more detail? I’m guessing you still have Spring Boot deps on the classpath, but you have just disabled auto configuration?

My first thought is If you want to venture outside of Spring Boot world, you might be better off using Spring Security 5.1 directly. You would loose the custom parsing of the groups claim, but I can point you how to add that back in if you need it. (and provide suggestions on how to better handle the access token validation, over the default claim checks)

Either way, let me know.


#3

Thanks for the quick response!

I am not currently using any other Spring Boot libraries, so I would be more than happy to ditch the okta-spring-boot-starter dependency and use as little “magic” as possible. (I assume I would keep the okta-spring-sdk dependency?)

Here are the various versions of Spring components that I am using:

    <springframework.version>4.1.2.RELEASE</springframework.version>
    <springframework.security.version>3.2.5.RELEASE</springframework.security.version>
    <springframework.ws.version>2.2.0.RELEASE</springframework.ws.version>
    <springframework.data.version>1.4.1.RELEASE</springframework.data.version>

For now I am trying to get the bare-minimum working.


#4

My suggestion if you are not using Boot is jump to Spring 5+ and Spring Sec 5+. (We are in the process of updating our libs to these versions too).

Our libs (both the current version and the next version) just configure Spring Security for Okta’s best practices.
For example, Spring Security 5.1 has local JWT token support, our lib will just add extra claims validation.
But we have a dependency on Boot: https://github.com/okta/okta-spring-boot/blob/spring-boot-2.1/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java#L53-L59

Does that help?


#5

OK, I upgraded spring…

    <springframework.version>5.1.1.RELEASE</springframework.version>
    <springframework.security.version>5.1.1.RELEASE</springframework.security.version>
    <springframework.ws.version>2.2.0.RELEASE</springframework.ws.version>
    <springframework.data.version>1.4.1.RELEASE</springframework.data.version>
    <jackson.version>2.9.7</jackson.version>

… and worked through all of the resulting issues it caused. I also changed the jwksUrl because I was missing the “default” ealier. This is what my new jwksUrl:

https://<my dev subdomain>.oktapreview.com/oauth2/default/v1/keys

For clarity, I do NOT have the okta-spring-boot-starter dependency, but I DO have the okta-spring-sdk dependency:

    <dependency>
        <groupId>com.okta.spring</groupId>
        <artifactId>okta-spring-sdk</artifactId>
        <version>0.6.1</version>
    </dependency>
    <!--<dependency>-->
        <!--<groupId>com.okta.spring</groupId>-->
        <!--<artifactId>okta-spring-boot-starter</artifactId>-->
        <!--<version>0.6.1</version>-->

        <!--<exclusions>-->
            <!--<exclusion>-->
                <!--<groupId>ch.qos.logback</groupId>-->
                <!--<artifactId>logback-classic</artifactId>-->
            <!--</exclusion>-->
            <!--<exclusion>-->
                <!--<groupId>org.slf4j</groupId>-->
                <!--<artifactId>log4j-over-slf4j</artifactId>-->
            <!--</exclusion>-->
        <!--</exclusions>-->
    <!--</dependency>-->

Now I am getting this response when trying to access my protected resource:

{
	"error": "access_denied",
	"error_description": "Invalid token does not contain resource id (0oah35d952zE3My9x0h7)"
}

When I step through the authorization process in the debugger, I can see it throws an OAuth2AccessDeniedException on line 59 in OAuth2AuthenticationManager:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    if (authentication == null) {
        throw new InvalidTokenException("Invalid token (token not found)");
    } else {
        String token = (String)authentication.getPrincipal();
        OAuth2Authentication auth = this.tokenServices.loadAuthentication(token);
        if (auth == null) {
            throw new InvalidTokenException("Invalid token: " + token);
        } else {
            Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
            if (this.resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(this.resourceId)) {
                ****
                HERE --> throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + this.resourceId + ")");
                ****
            } else {
                this.checkClientDetails(auth);
                if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
                    OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
                    if (!details.equals(auth.getDetails())) {
                        details.setDecodedDetails(auth.getDetails());
                    }
                }

                auth.setDetails(authentication.getDetails());
                auth.setAuthenticated(true);
                return auth;
            }
        }
    }
}

It is doing so because the Client Id is not in the resourceIds HashSet. (the resourceIds has one entry, and it is “api://default”

So that’s where I’m at. Any clues?


#6

Oh FFS, I just spent 30 minutes writing a detailed reply and it went poof when I submitted it. :face_with_symbols_over_mouth:

I’ll try again in the morning.

Spoiler Alert: I upgraded Spring but still have problems.


#7

I hear ya! Keep us posted!


#8

I see a message in my inbox saying that long-winded post was flagged as spam. Can you find it and “unspam” it?


#9

I’ll track down someone who can!


#10

I was able to move past one roadblock by going into the Okta Dashboard and setting the audience to be the same value as the Client Id. Since I will not be able to do that in a Production environment, the next thing I need to know is how to specify the audience on my server, as a spring config value or something. Can you help me with that?


#11

I think I have it working now!

Apparently my problem all along was that I was using the Client Id as the resourceId, when I guess I should have been using the audience as the resourceId.

Now I just have to figure out how to map scopes to Spring authorities. (right?)


#12

@JoeJErnst still on Sprint 4.x?

If so you should be able to add an AuthoritiesExtractor bean similar to this:

I do want to warn you though, Spring Security 5+ OAuth implementation is radically different (I really like the improvements), but just know you will need to reimplement this logic in the future.

Does that help?


#13

I see my original post finally showed up.

I upgraded to Spring 5.1.1. How would I extract the scopes and transform them into GrantedAuthorities?


#14

With Spring Security 5.1 scopes just work when with resource servers the authority to check for would be SCOPE_yourvalue assuming the scope was yourvalue


#15

Ahhh, more magic! OK, here’s the decoded token I am getting:

{
  "ver": 1,
  "jti": "AT.DD1xbMFObzZs8QL6xNW2NxCq7d--vuZj0gJv97Qv3qg",
  "iss": "<authorization server uri>",
  "aud": "fortellis",
  "iat": 1541706660,
  "exp": 1541710260,
  "cid": "<hash value>",
  "uid": "<hash value>",
  "scp": [
	"openid"
  ],
  "sub": "<my email address>"
}

So would I would annotate my Resource with this:

@GET
@RolesAllowed("SCOPE_openid")
public Response methodName() {...

That’s what I have right now and I’m getting a 401 Unauthorized.


#16

Alright, I think I got this! Here’s what I have now and it appears to be working:

@GET
@PreAuthorize("#oauth2.hasScope('openid')")