Skip to content
Advertisement

OAuth2 authorization code flow: spring-security does not accept the issued access_token

I am learning the OAuth2 authorization code flow.

  • I have my own Authorization Server (AS) which is OpenAM 7.1.
  • The Client is a simple Spring-Boot web application with a static HTML page, I use Spring-Security to protect the HTML page and control the Oauth2 flow.

I think that my Authorization Server configuration is correct because AS produces the access_token at the end when I simulate the communication with CURL. But somehow Spring-Security does not want to accept the issued and validated access token. So I think that my Spring-Security configuration is not correct.

I tried to configure Spring-Security in many different ways, but unfortunately, none of them was working. Maybe I need to implement the steps that I execute with CURL with Spring-Security, but maybe I just missed a configuration line.

This is the last step of my CURL chain where AS gives me the access token (exchange the authorization code for an access token):

url="$authServerHost/openam/oauth2/realms/root/access_token"

curl 
  --silent 
  --dump-header - 
  --insecur 
  --request POST 
  --data "grant_type=authorization_code" 
  --data "code=$authorizationCode" 
  --data "client_id=$clientId" 
  --data "client_secret=$clientSecret" 
  --data "redirect_uri=$redirectUri" 
  "$url"

-------- response ---------
HTTP/1.1 200 
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Content-Length: 157
Date: Wed, 29 Sep 2021 17:57:30 GMT

{
    "access_token":"2SiD3moh2iql5j3ocdPOR-W4QRE",
    "refresh_token":"zWSG-fi1J9hUrY0Tw6GHeXnndgM",
    "scope":"public_profile",
    "token_type":"Bearer",
    "expires_in":3599
}

This is the probe of the token (validate and retrieve information about the token):

url="$authServerHost/openam/oauth2/tokeninfo"
curl 
  --silent 
  --dump-header - 
  --insecur 
  --request GET 
  --header "Authorization: Bearer $accessToken" 
  "$url"

-------- response ---------
HTTP/1.1 200 
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Content-Length: 326
Date: Wed, 29 Sep 2021 17:57:30 GMT

{
   "access_token":"2SiD3moh2iql5j3ocdPOR-W4QRE",
   "grant_type":"authorization_code",
   "auth_level":0,
   "auditTrackingId":"45f24ab1-f9a4-43df-bb17-4b4d6c0ffee4-112239",
   "scope":["public_profile"],
   "public_profile":"",
   "realm":"/",
   "token_type":"Bearer",
   "expires_in":3599,
   "authGrantId":"2tg__erKRo_utv4Py_TOt1NOtDo",
   "client_id":"hello-web"
}

Then I try to fetch the protected content with this CURL but Spring-Security redirects me again to the Spring Security’s provider selection page:

url="https://web.example.com:8444/user.html"
curl 
  --silent 
  --insecur 
  --dump-header - 
  --request GET 
  --header "Authorization: Bearer $accessToken" 
  "$url"

-------- response ---------
HTTP/1.1 302 
Cache-Control: private
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Location: https://web.example.com:8444/oauth2/authorization/openam
Content-Length: 0
Date: Wed, 29 Sep 2021 18:18:40 GMT

As you can see my web-app with Spring-Security does not accept the valid bearer token and redirects the request to the Authorization Server despite I provided the token.

This is my Spring-Security configuration:

spring:
  security:
    oauth2:
      client:
        registration:
          openam:
            client-id: hello-web
            client-secret: client-secret
            authorization-grant-type: authorization_code
            redirect-uri: https://web.example.com:8444/user.html
            scope: public_profile
        provider:
          openam:
            token-uri: https://openam.example.com:8443/openam/oauth2/access_token
            authorization-uri: https://openam.example.com:8443/openam/oauth2/authorize

And the Spring-Security config I use:

@EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .antMatchers("/").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(withDefaults());
    }
}

What did I miss? Could you please guide me in the right direction?

—-> ADDITIONAL INFO <—-

When I open the protected page https://web.example.com:8444/user.html in the web browser, then

  1. I am redirected properly to the Authorization Server login page.
  2. Then I log in.
  3. Then the consent approval form appears where I give access to the “public_profile” scope
  4. Then Spring redirects me again to the login page (step 2).

I think that happens because Spring just does not want to accept the issued access token.

Spring log:

open http://web.example.com:8081/user.html

o.s.security.web.FilterChainProxy        : Securing GET /user.html
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [GET /user.html] with attributes [authenticated]
o.s.s.w.s.HttpSessionRequestCache        : Saved request https://web.example.com:8444/user.html to session
s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], Not [And [Or [Ant [pattern='/login'], Ant [pattern='/favicon.ico']], And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f2273f8, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]
s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@23fec83b
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://web.example.com:8444/oauth2/authorization/openam
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/openam
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://openam.example.com:8443/openam/oauth2/authorize?response_type=code&client_id=example-web&scope=public_profile&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&redirect_uri=https://web.example.com:8444/user.html
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

then the Authorization Server's login page appears
then I allow access to the "public_profile" scope

then this happens on the Spring side:

o.s.security.web.FilterChainProxy        : Securing GET /user.html?code=1aC6OC44B03k37pACmmD8n-Ol38&iss=https%3A%2F%2Fopenam.example.com%3A8443%2Fopenam%2Foauth2&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&client_id=example-web
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [GET /user.html?code=1aC6OC44B03k37pACmmD8n-Ol38&iss=https%3A%2F%2Fopenam.example.com%3A8443%2Fopenam%2Foauth2&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&client_id=example-web] with attributes [authenticated]
o.s.s.w.s.HttpSessionRequestCache        : Saved request https://web.example.com:8444/user.html?code=1aC6OC44B03k37pACmmD8n-Ol38&iss=https%3A%2F%2Fopenam.example.com%3A8443%2Fopenam%2Foauth2&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&client_id=example-web to session
s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], Not [And [Or [Ant [pattern='/login'], Ant [pattern='/favicon.ico']], And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f2273f8, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]
s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@23fec83b
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://web.example.com:8444/oauth2/authorization/openam
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/openam
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://openam.example.com:8443/openam/oauth2/authorize?response_type=code&client_id=example-web&scope=public_profile&state=SRHzhN7krlfUhJ50m9lsvaWgLUHglgMOA0wMZHrAmYo%3D&redirect_uri=https://web.example.com:8444/user.html
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

Advertisement

Answer

I notice two issues in the code you have shared.

The first is that you may be confusing an OAuth 2.0 resource server and an OAuth 2.0 client.
The application running on web.example.com:8444 is configured as an OAuth 2.0 client.
However, you are making a request to web.example.com:8444, providing a bearer token and asking for a resource.
The client will not validate the bearer token. In this scenario it seems like you are treating the application as if it were a resource server.

If you are looking to create a resource server application, you can see the full documentation in the Spring Security reference.

The second issue is the behaviour you described when accessing the client in the browser.
The problem here is customising redirect-uri: https://web.example.com:8444/user.html.

When doing this you override the default redirect URI, which is /login/oauth2/callback/{registrationId}.

This URI is special because it prompts the OAuth2LoginAuthenticationFilter to process the request, attempt to authenticate the user and create the OAuth2AuthenticationToken.

When you customise redirect URI, the OAuth2LoginAuthenticationFilter is not invoked and the application does not know if the user is authenticated.

Advertisement