0
respostas

[Sugestão] Melhor forma de armazenar o RefreshToken

O RefreshToken é uma chave extremamente poderosa, que se vazada, pode acarretar em um grande desastre. Então trago uma sugestão que senti falta durante a aula que é transporta-la via Cookie HTTPOnly. Essa estratégia barra qualquer java script malicioso que tenha acesso ao refresh token ou de forma geral a attacks do tipo XSS.

Durante as aulas foi aprensentada a opção de retornar o access e o refresh tokens no corpo da requisição, o que torna acessível aos ataques mencionados acima. Abaixo mostro um exemplo e sugestão para os próximos alunos usarem ou para que algum instrutor possa atualizar o curso pois considero a segurança a parte mais importante de uma api.

@RestController
@RequestMapping("/authenticate")
public class AuthenticationController {

    @Autowired
    private TokenManager tokenManager;

    @Autowired
    private AuthenticationManager manager;
    
    @Autowired
    private UserRepository userRepository;

    @PostMapping
    public ResponseEntity userAuthentication(@RequestBody @Valid UserLoginDTO userLoginDTO, HttpServletResponse request) {
        
        var token = new UsernamePasswordAuthenticationToken(userLoginDto.getEmail(), userLoginDto.getPassword());
        var authentication = manager.authenticate(token);
        User user = ((SystemUser) authentication.getPrincipal()).getUser();


        // Gera o access token
        TokenDTO tokenJWT = new TokenDTO(tokenManager.generateAccessToken(user));
        
        // Gera o refresh token
        var newRefreshToken = tokenManager.generateToken(user, 24 * ONE_HOUR_IN_MINUTES);

        // Cria o cookie com o refresh token
        ResponseCookie cookie = ResponseCookie.from("refresh_token", newRefreshToken)
                .httpOnly(true)
                .secure(true)
                .sameSite("Strict")
                .path("/authenticate/refresh")
                .maxAge(Duration.ofMinutes(3))
                .build();

        // Salva o cookie no header
        request.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

        // Retorna o access token no corpo da requisição
        return ResponseEntity.ok(tokenJWT);
    }

    @GetMapping("/refresh")
    public ResponseEntity userRefreshAccessToken(@CookieValue("refresh_token") String refreshToken) {
        String userEmail = tokenManager.getSubject(refreshToken);
        User user = userRepository.findByEmail(userEmail).orElseThrow(() -> new UsernameNotFoundException("User subject token invalid."));

        var tokenJWT = new TokenDTO(tokenManager.generateAccessToken(user));

        return ResponseEntity.ok(tokenJWT);
    }
}

No exemplo acima temos o método Post onde o recebe o usuário e senha, gera o access token, gera o refresh token e incorpora ele em um cookie http only seguro e retorna apenas o access token no corpo da requisição.

Abaixo no método Get "/refresh", é usado o cookie para pegar o refresh token e gerar um novo access token e retornar para o client sem que o client se preocupe de manipular o refresh token.

Essa estratégia impede um monte de dor de cabeça, mas ainda não é tudo, existem mais camadas de proteção que podemos implementar para deixar nossa segurança ainda mais robusta, uma delas é o rotation refresh token que deixo como dica para quem quiser se aprofundar mais ainda.

Por último alguns links de referências:
https://dev.to/khaledsaeed18/access-and-refresh-tokens-in-token-based-authentication-2a9o
https://docs.descope.com/security-best-practices/refresh-token-storage

Espero ter contribuido um pouco com esta seleta comunidade e estou aberto a críticas ou sugestões.