2

I have an application (not reactive) with Angular UI, Zuul and a few Services which are integrated with Okta login (OAuth). This works fine but is stuck after the jwt token expires. Some details about the workflow

  1. The app URL is pointing to Zuul.
  2. Zuul redirects the request to Okta. User logs in.
  3. Okta sends a Bearer token (also a refresh token) back.
  4. This Bearer token is passed to the UI and is stored as a cookie. With every request the UI sends the Authorization header, with the bearer token.
  5. This process works fine till the jwt token expires in an hour and then Zuul tries to redirect it to the default login page, which has nothing as we use the okta login.

The questions I have

  1. Where can the loging page be redirected, if needed https://dev1234.okta.com/oauth2/default?  
  2. How to get a new bearer token based on the refresh token?
  3. Can I get the new bearer token in Zuul automatically based on the refresh token.If this is not possible what is the best approach?

Here is the application.yml file for Zuul

spring:
  application:
    name: service-gateway
  cloud:
    loadbalancer:
      ribbon:
        enabled: false

server:
  port: 8080

okta:
  oauth2:
    issuer: https://dev1234.okta.com/oauth2/default
    client-id: <value>
    client-secret: <value>

    
feign:
  hystrix:
    enabled: true
hystrix:
  shareSecurityContext: true

eureka:
  client:
    enabled: true
    fetch-registry: true
    
zuul:
  routes:
    abc-service:
      path: /api/**
      strip-prefix: true
      service-id: ABC-SERVICE

    ui:
      path: /**
      url: http://localhost:4200
      
  host:
    connect-timeout-millis: 10000
    socket-timeout-millis: 20000
    
  sensitive-headers:
  - Cookie,Set-Cookie  

The gradle file

plugins {
    id 'org.springframework.boot' version '2.3.7.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudServicesVersion', "2.3.0.RELEASE")
    set('springCloudVersion', "Hoxton.SR9")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'com.okta.spring:okta-spring-boot-starter:1.4.0'
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-zuul'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-config-client'
    implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

dependencyManagement {
    imports {
        mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:${springCloudServicesVersion}"
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

test {
    useJUnitPlatform()
}

The Zulu WebSecurityConfigurerAdapter

package abc
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication

public class ServiceGateway {

    private static List<String> clients = Arrays.asList("okta");
    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @Value("${okta.oauth2.client-id}")
    String clientId;
    @Value("${okta.oauth2.client-secret}")
    String clientSecret;

    public static void main(String[] args) {
        SpringApplication.run(ServiceGateway.class, args);
    }

    @Configuration
    static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .oauth2Login()
            .and()
            .oauth2ResourceServer().jwt();
            // @formatter:on
        }
    }

    @Bean
    public FilterRegistrationBean<CorsFilter> simpleCorsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedOrigins(Collections.singletonList("*"));
        config.setAllowedMethods(Collections.singletonList("*"));
        config.setAllowedHeaders(Collections.singletonList("*"));
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }

    @Bean
    public OktaAuthenticationFilter oktaAuthFilter(OAuth2AuthorizedClientService clientService) {
        return new OktaAuthenticationFilter(clientService);
    }


The service application.yml

spring:
  application:
    name: analytics-service

server:
  port: 8081

eureka:
  client:
    enabled: true

okta:
  oauth2:
    issuer: https://dev1234.okta.com/oauth2/default
    client-id: <value>
    client-secret: <value>

The config file for service

@Configuration
    static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                .authorizeRequests().anyRequest().authenticated()
                    .and()
                    .oauth2ResourceServer().jwt();
            // @formatter:on
        }
    }

    @Bean
    protected RestTemplate restTemplate() {
        return new OAuth2RestTemplate(oAuthDetails());
    }

The Gradle file

buildscript {
    dependencies {
        classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0')
    }
}

plugins {
    id 'org.springframework.boot' version '2.3.7.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'
apply plugin: 'com.palantir.docker'

group = 'com.demo'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }
}

ext {
    set('springCloudVersion', "Hoxton.SR9")
    set('springCloudServicesVersion', "2.3.0.RELEASE")
}

dependencies {
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-config-client'
    implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure'
    implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    implementation 'com.okta.spring:okta-spring-boot-starter:1.4.0'
    compileOnly    'org.projectlombok:lombok'
    implementation    'com.googlecode.json-simple:json-simple:1.1.1'
    annotationProcessor 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    testImplementation 'org.mockito:mockito-core:2.7.22'
}

dependencyManagement {
    imports {
        mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:${springCloudServicesVersion}"
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

task unpack(type: Copy) {
    dependsOn bootJar
    from(zipTree(tasks.bootJar.outputs.files.singleFile))
    into("build/dependency")
}
docker {
    name "${project.group}/${bootJar.baseName}"
    copySpec.from(tasks.unpack.outputs).into("dependency")
    buildArgs(['DEPENDENCY': "dependency"])
}

task standardTests(type: Test) {
    useJUnitPlatform {
    }
}  

Update Looks like the Scope "offline_access" did make some difference. Now I am getting a CORS error. With the filter in place should I not see this? Is this due to the presence of the authorization header? Also if I go and refresh the browser manually a new token is provided. No CORS issue then

zone.js:3243 POST http://localhost:8080/api/system/summary/salesHierarchy 401
core.js:15724 ERROR 
HttpErrorResponse {headers: HttpHeaders, status: 401, statusText: "OK", url: "http://localhost:8080/api/system/summary/salesHierarchy", ok: false, …}
:8080/verification:1 Access to XMLHttpRequest at 'https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&…0/login/oauth2/code/okta&nonce=4aoYCPl3OKhsOTTpCUiqayYjQXdpZLuonn6_Q6193-o' (redirected from 'http://localhost:8080/verification') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
zone.js:3243 GET https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&…0/login/oauth2/code/okta&nonce=4aoYCPl3OKhsOTTpCUiqayYjQXdpZLuonn6_Q6193-o net::ERR_FAILED
core.js:15724 ERROR 
HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "http://localhost:8080/verification", ok: false, …}

Update1 When the JWT token expires I see the error below, which is expected.

2021-01-31 10:38:53 [http-nio-8080-exec-9] DEBUG o.s.s.o.s.r.w.BearerTokenAuthenticationFilter - Authentication request for failed!
org.springframework.security.oauth2.server.resource.InvalidBearerTokenException: An error occurred while attempting to decode the Jwt: Jwt expired at 2021-01-31T16:35:39Z

The 401 is sent to the UI and UI captures it and redirects to another url in the APP. Thses are the gateway logs

2021-01-31 10:38:53 [http-nio-8080-exec-10] DEBUG o.s.s.w.s.HttpSessionRequestCache - DefaultSavedRequest added to Session: DefaultSavedRequest[http://localhost:8080/verification]
2021-01-31 10:38:53 [http-nio-8080-exec-10] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
org.springframework.security.web.authentication.WebAuthenticationDetails@2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: A55D113FD46A0031CA1FADD21C008382; Granted Authorities: ROLE_ANONYMOUS
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@4180b4a8, returned: -1
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 1 of 17 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 2 of 17 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@422bdd20. A new one will be created.
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 3 of 17 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 4 of 17 in additional filter chain; firing Filter: 'CorsFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)
    at 
......

o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Trying to match using AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using org.springframework.security.web.csrf.CsrfFilter$DefaultRequiresCsrfMatcher@3f60847a
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Did not match
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = true
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 6 of 17 in additional filter chain; firing Filter: 'LogoutFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET /oauth2/authorization/okta' doesn't match 'POST /logout'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - /oauth2/authorization/okta at position 7 of 17 in additional filter chain; firing Filter: 'OAuth2AuthorizationRequestRedirectFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth2/authorization/okta'; against '/oauth2/authorization/{registrationId}'
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/oauth2/authorization/okta'; against '/oauth2/authorization/{registrationId}'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - httpRequestMediaTypes=[image/avif, image/webp, image/apng, image/svg+xml, image/*, */*;q=0.8]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - Processing image/avif
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - application/xhtml+xml .isCompatibleWith image/avif = false
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - image/* .isCompatibleWith image/avif = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - All requestMatchers returned true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Match found! Executing org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint@1c10eb3a
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - Trying to match using AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], NegatedRequestMatcher [requestMatcher=AndRequestMatcher [requestMatchers=[OrRequestMatcher [requestMatchers=[Ant [pattern='/login'], Ant [pattern='/favicon.ico']]], AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=AndRequestMatcher [requestMatchers=[OrRequestMatcher [requestMatchers=[Ant [pattern='/login'], Ant [pattern='/favicon.ico']]], AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using OrRequestMatcher [requestMatchers=[Ant [pattern='/login'], Ant [pattern='/favicon.ico']]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/login']
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/favicon.ico'; against '/login'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/favicon.ico']
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/favicon.ico'; against '/favicon.ico'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.OrRequestMatcher - matched
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using AndRequestMatcher [requestMatchers=[NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f86a06a, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - httpRequestMediaTypes=[image/avif, image/webp, image/apng, image/svg+xml, image/*, */*;q=0.8]
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.web.DefaultRedirectStrategy - Redirecting to 'https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oa2amci5xaQcrWbF357&scope=openid%20profile%20email%20address%20phone%20offline_access&state=yXMfdZoYPFl3yoISRnJLftlFnXmf3AnBgnUdGk0MBAc%3D&redirect_uri=http://localhost:8080/login/oauth2/code/okta&nonce=sKf92sk_wHRT6Zq1XwEw-NdYwik-CRMgXLZa_3jfKpA'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - Processing image/avif
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - application/xhtml+xml .isCompatibleWith image/avif = false
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@61505c39
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.MediaTypeRequestMatcher - image/* .isCompatibleWith image/avif = true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - All requestMatchers returned true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - All requestMatchers returned true
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.m.NegatedRequestMatcher - matches = false
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Did not match
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.a.DelegatingAuthenticationEntryPoint - No match found. Using default entry point org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@693f1240
2021-01-31 10:38:53 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.web.DefaultRedirectStrategy - Redirecting to 'http://localhost:8080/login'
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@61505c39
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2021-01-31 10:38:53 [http-nio-8080-exec-8] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 1 of 17 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 2 of 17 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@422bdd20. A new one will be created.
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 3 of 17 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 4 of 17 in additional filter chain; firing Filter: 'CorsFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] TRACE o.s.c.n.zuul.web.ZuulHandlerMapping - Mapped to HandlerExecutionChain with [org.springframework.cloud.netflix.zuul.web.ZuulController@3ce8f88f] and 1 interceptors
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 5 of 17 in additional filter chain; firing Filter: 'CsrfFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Trying to match using org.springframework.security.web.csrf.CsrfFilter$DefaultRequiresCsrfMatcher@3f60847a
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.matcher.AndRequestMatcher - Did not match
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 6 of 17 in additional filter chain; firing Filter: 'LogoutFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET /login' doesn't match 'POST /logout'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 7 of 17 in additional filter chain; firing Filter: 'OAuth2AuthorizationRequestRedirectFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/oauth2/authorization/{registrationId}'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 8 of 17 in additional filter chain; firing Filter: 'OAuth2LoginAuthenticationFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/login'; against '/login/oauth2/code/*'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - /login at position 9 of 17 in additional filter chain; firing Filter: 'DefaultLoginPageGeneratingFilter'
2021-01-31 10:38:53 [http-nio-8080-exec-6] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@61505c39

On the browser console I see the error

Access to XMLHttpRequest at 'https://dev-770454.okta.com/oauth2/default/v1/authorize?response_type=code&client_id=0oa2amci5xaQcrWbF357&scope=openid%20profile%20email%20address%20phone%20offline_access&state=yXMfdZoYPFl3yoISRnJLftlFnXmf3AnBgnUdGk0MBAc%3D&redirect_uri=http://localhost:8080/login/oauth2/code/okta&nonce=sKf92sk_wHRT6Zq1XwEw-NdYwik-CRMgXLZa_3jfKpA' (redirected from 'http://localhost:8080/verification') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Should this CORS error not be handled by the filter defined on the Gateway Or is CORS filter in not being applied ? What confuses me is that if I go to the browser and click on the refresh button it works fine.

Tech
  • 33
  • 7

2 Answers2

2

From Spring Security's documentation:

The OAuth2RefreshToken may optionally be returned in the Access Token Response for the authorization_code and password grant types. If the OAuth2AuthorizedClient.getRefreshToken() is available and the OAuth2AuthorizedClient.getAccessToken() is expired, it will automatically be refreshed by the RefreshTokenOAuth2AuthorizedClientProvider.

Checking the refresh token box in your Okta app is not enough to get a refresh token. You also need to pass in an offline_access scope or it won't be returned.

I'd recommend you upgrade to the Okta Spring Boot starter 2.0.0 and use the following scopes property:

okta.oauth2.scopes=openid, email, profile, offline_access
Matt Raible
  • 8,187
  • 9
  • 61
  • 120
  • I have both in the tokens in the OAuth2AuthorizedClient. The scopes of offline_access is what is missing. Let me try that. – Tech Jan 28 '21 at 19:13
  • now I have a CORS issue, shouldnt the CORS filter take care of it? – Tech Jan 29 '21 at 04:42
  • You have a CORS error with Okta or with your API? Can you please add the error / update your original question? – Matt Raible Jan 30 '21 at 02:51
  • The CORS error seems to be from the Gateway. I dont see error logs in Okta. Have update the logs in update1 – Tech Jan 31 '21 at 17:19
  • 1
    If the CORS error is coming from Okta, you need to add `http://localhost:8080` in your Okta developer dashboard at API > Trusted Origins. If it's coming from your gateway, you need to configure CORS for Spring Security. https://stackoverflow.com/a/60962180/65681 – Matt Raible Jan 31 '21 at 17:22
  • I already have that "http://localhost:8080/" and both options for CORS and Redirect are selected in Okta, will try the Spring Security Option – Tech Jan 31 '21 at 17:36
  • Did you configure CORS in your gateway? That's the only thing I can think of without trying myself. This shows how I've done it in Spring Cloud Gateway: https://developer.okta.com/blog/2019/08/28/reactive-microservices-spring-cloud-gateway#cors-with-spring-cloud-gateway – Matt Raible Jan 31 '21 at 17:38
  • Thank you Matt for the suggestions. Nothing worked, I think it is an issue with the CORS filter not behaving as expected. Not sure why the CORS issue would come into play only after the token refresh though. The app works fine till the token expires and after that we get the CORS exception, refreshing the browser makes things work fine till the token again expires – Tech Feb 01 '21 at 15:02
0

If you have the refresh token, you could send it to the /token endpoint to get a new access token. You'd just want to decide if you fetch the new token pre-emptively (based on when it will expire) or re-actively (requesting a new access token if the existing one stops working).

Andrea
  • 51
  • 1
  • 2
  • 5
  • I did write a zuul filter to do this and was able to get the new token. After sending the token to the UI and storing it in a cookie. When a new request was made with the new token the error. 2021-01-28 11:45:43 [http-nio-8080-exec-1] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point org.springframework.security.access.AccessDeniedException: Access is denied – Tech Jan 28 '21 at 19:18