driftwood
driftwood

Reputation: 2121

how to extract access_token from oauth2 flow in spring boot client

I need to understand how to access and extract the access_token from a spring boot client app. So I created a spring boot app that does the oauth2 flow against the github oauth2 provider and it works to authorize and authenticate my spring boot app no problem. BUT, what I also need is to have my app be able to extract the access_token. This is what I'm having trouble with...I don't understand how the redirection would look like in a spring boot app. I tried to get an idea from this SO post OAuth2: Extract access_token fragment from implicit flow...but not sure how this would translate in a spring boot app.

Here the client code with what I've tried so far and the github config below the spring boot client code:

application.yml:

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: ea54290955115164855a
            client-secret: d4987ca45405bb75adc78175f9aa5c11ab9e2b2a
            scope: user:email
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/github'
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token

SecurityConfig.java:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers("/").permitAll();
                    auth.requestMatchers("/favicon.ico").permitAll();
                    auth.anyRequest().authenticated();
                })
                .oauth2Login(withDefaults())
                .formLogin(withDefaults())
                .build();

    }
}

AuthController.java:

@RestController
public class AuthController {

    @GetMapping("/login/github")
    public String redirectToGitHub() {
        // This will automatically redirect the user to the GitHub authorization URL
        return "redirect:https://github.com/login/oauth/authorize";
    }

    @GetMapping("/login/oauth2/code/github")
    public String handleGitHubCallback(@AuthenticationPrincipal OAuth2User principal) {
        // Handle the callback and extract the access token
        String accessToken = principal.getAttribute("access_token");

        // Use the access token for GitHub API requests or store it as needed
        // Redirect or display user information as required

        return "redirect:/profile"; // Redirect to a profile page, for example
    }
}

The /login/oauth2/code/github endpoint is where I thought the browser would re-direct to but it doesn't...why?

And this is how I registered the client on the github side:

enter image description here

Upvotes: 1

Views: 1686

Answers (1)

ch4mp
ch4mp

Reputation: 12774

The authorization-code is not an access token. It is a code that can be used only once by the OAuth2 client to get the tokens. So, when /login/oauth2/code/github is called, the client does not have the tokens yet.

Once a client was successfully authorized for a given client registration (after the code was exchanged for tokens), you can get an OAuth2AuthorizedClient instance for that registration from the OAuth2AuthorizedClientRepository. OAuth2AuthorizedClient contains the access token you are looking for.

@RestController
@RequiredArgsConstructor
static class TokensController {
    private final OAuth2AuthorizedClientRepository authorizedClientRepo;
    
    @GetMapping("/access-token")
    @PreAuthorize("isAuthenticated()")
    public String getAccessToken(HttpServletRequest request, OAuth2AuthenticationToken auth) {
        final var authorizedClient = authorizedClientRepo.loadAuthorizedClient(auth.getAuthorizedClientRegistrationId(), auth, request);
        return Optional.ofNullable(authorizedClient).map(OAuth2AuthorizedClient::getAccessToken).map(OAuth2AccessToken::getTokenValue).orElse(null);
    }
}

Please note that what you are trying to do is very suspicious and will probably open serious security breaches: access tokens are delivered to a client to act on behalf of a resource owner on the resource servers part of the token audience. If the token is leaked, malicious programs / users can steal resource owner identity and authorize malicious requests to the resource servers. So, access tokens should be exchanged only between the authorization server issuing it, the client to which it is delivered and the resource server(s) part of the audience. It should not be accessible to frontends as you are probably implementing, other clients, resource servers not part of the audience, etc.. You'd better be using something like the TokenRelay filter in spring-cloud-gateway which replaces the session cookie with an Authorization header containing the access token in session, before forwarding a request from the frontend to a resource server. As reminder, requests processed by a security filter-chain with oauth2Login are secured with sessions, not tokens.

So instead of exposing access tokens to your frontend, you should probably be implementing the BFF pattern like I do in this tutorial.

Upvotes: 0

Related Questions