1

TL;DR - How do I use a custom built JWT to protect my API WITHOUT using oauth2?

I've read this question but I'm still not sure how to untangle OAuth2 from JWT authentication in Spring Security 5. I have a strange case I'll try to explain

  1. my SPA will authenticate with Okta to get an access token
  2. my SPA will send make a GET to my boot API (that I control) at /token/exchange, which will return a custom built JWT. My /token/exchange route is projected via oauth2 using the okta spring boot starter. I have that part working.
  3. my SPA will send the custom JWT as an Authorization header on all subsequent requests to my API. I need to protect all routes starting with /api so that only requests with my custom token are accepted.

I'll spare the why unless someone really wants the details....but please trust me when I say I'm stuck between a rock and a hard place and this is how we've decided to solve the issue.

I understand I'll need two WebSecurityConfigurerAdapter so I can protect my /token/exchange route with Okta while protecting all my other routes with my custom JWT.

I'm confused about the security filter details. Should I override one? Which one? Should I be making a brand new filter? Where do I insert it? I've seen example online that use GenericFilter, OncePerRequestFilter, UsernameAndPasswordFilter, BasicAuthFilter. I'm a bit overwhelmed with options...

to me, +1's would indicate an opportunity to serve the community and showcases a need that the spring team could help fill.

------ the gory details ------

my SPA currently has a custom login page that submits the creds to my API. My API make a REST call to Ping Federate to get a JWT using the grant type password. This JWT is returned to the SPA which the SPA uses for all subsequent requests.

I've been asked to convert the app to use the parent company's Okta instance. I don't control it and I can't get it to return me a toke that will exactly match the Ping Federate token my app is currently expecting.

my SPA is an angular 1.5 app. before it's manually bootstrapped, I plan to do the okta auth via redirect and exchange the Okta token for a custom token. Basically, instead of passing in my users creds, I can pass in my token as a sort of voucher...because I'll never see the users creds anymore....they will only be entered at Okta...which is sort of the whole point.

In my API, simply by using the subject of the okta token, I can lookup all the needed data and construct a token that looks identical in shape to the token provided by Ping Federate (which does not return a typical oauth2 token). By getting a token that matches what I am currently getting, most of the API logic can continue to work "as is".

-------- Failed attempts thus far

I see this in my logs

o.s.security.web.FilterChainProxy        : /api/entities at position 6 of 12 in additional filter chain; firing Filter: 'BearerTokenAuthenticationFilter'
o.s.s.authentication.ProviderManager     : Authentication attempt using org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider
.s.a.DefaultAuthenticationEventPublisher : No event was found for the exception org.springframework.security.oauth2.core.OAuth2AuthenticationException
.o.s.r.w.BearerTokenAuthenticationFilter : Authentication request for failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@3ec543f6
s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

this seems like spring security is trying to authenticate requests made to /api/entities with my Okta oauth config, instead of with my "ApiConfig", which for now, I'm just making my entire /api/** public

@Configuration
@Order(1)
public class OktaWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/token/exchange")
                .authorizeRequests()
                .antMatchers("/token/exchange").authenticated()
                .and()
                .oauth2ResourceServer().jwt();
        http.cors();
        http.csrf().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // https://github.com/okta/okta-spring-boot/blob/master/oauth2/src/main/java/com/okta/spring/boot/oauth/Okta.java
        Okta.configureResourceServer401ResponseBody(http);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }
}

and

@Configuration
@Order(200)
public class ApiWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/public/**","/api/**").permitAll()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
        http.csrf().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.cors();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }
}

any request to /token/exchange must have an Okta provided token. this seems to be working as I expect. any request to /api/** must have my custom created JWT. my SPA is sending the custom JWT, but in an effort to understand things better, I figured I could just set all the /api calls to permitAll and prove my Okta filter is not getting in the way.

with more debugging, it seems that BearerTokenAuthenticationFilter is getting called both both the /token/exchange call and the /api/entities call. Both times it's checking the token against the Okta Oauth provider. So it's failing on the /api/entities call which has my custom token....NOT the okta token

I'm so confused

Jason
  • 2,451
  • 2
  • 23
  • 31
  • 1 clarity, why do you have token/exchange on your server? When infact token exchange from auth server is happening with SPA using okta itself? the token you receive from okta will have scopes you can use those scope to prevent access to you apis. – silentsudo Jun 05 '20 at 14:54
  • @silentsudo I updated the question....is that enough of the details – Jason Jun 05 '20 at 15:24
  • the okta token has scopes...yes. The api is based on claims, not scopes and I will never be able to get the okta tokens to have the needed info in the claims or scopes. – Jason Jun 05 '20 at 15:26

1 Answers1

0

Not sure I understand correctly, so I try to summarize. You want to

  • retrieve a token from Okta with your SPA
  • exchange that token for a custom token generated by your API
  • Use the latter to communicate with the API.

Using 2 WebSecurityConfigurerAdapter implementations is one way to go about it and I think it's a good one. You would need 1 WebSecurityConfigurerAdapter that delegates to Okta and verifies that the token can be trusted on the exchange endpoint. Once it's verified, you can generate and return a token to the user.

The other WebSecurityConfigurerAdapter would be a simple one and you can find plenty of resources about it on the internet, but basically for each secure endpoint you need to verify the token.

I'm confused about the security filter details. Should I override one?

You can extend OncePerRequestFilter, as the name implies it will be invoked at most once for each request. This would verify that the request contains the header and it's a valid one, maps it to an Authentication and places it in the SecurityContextHolder

Where do I insert it?

You can insert it before/after the UsernamePasswordAuthenticationFilter, but you should probably read how the Spring Security architecture looks like to get a good idea.

Krisz
  • 1,884
  • 12
  • 17
  • "The other WebSecurityConfigurerAdapter would be a simple one and you can find plenty of resources about it on the internet, but basically for each secure endpoint you need to verify the token." -> yeah, there are TOO many examples all doing things differently.....that's my issue. I think I'm understand what I _CAN_ do....I'm confused on the reasons WHY I should use one strategy over the other – Jason Jun 05 '20 at 15:50
  • There are many different ways you can solve these problems with Spring Security, just stick to one. Basically you'll have to configure it to use your filter on the specified routes. – Krisz Jun 05 '20 at 15:56
  • @Krisz if we just implement resource server with `jwk-set-uri` and `issuer-uri` this solves the purpose unless we treat the application itself as oauth2client in that case also the token would be generated by okta, please correct me if I am wrong. – silentsudo Jun 05 '20 at 16:49
  • not sure I understand. Would you like to implement your own authroziation server that issues JWT tokens? – Krisz Jun 06 '20 at 08:20