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.