AuthenticationController.java

package com.siddharthgawas.apigateway.controller;

import com.siddharthgawas.apigateway.dto.TokenRefreshRequest;
import com.siddharthgawas.apigateway.security.dto.TokenDetails;
import com.siddharthgawas.apigateway.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;

/**
 * Controller for handling authentication-related requests.
 * <p>
 * This controller provides endpoints for generating and refreshing authentication tokens.
 */
@RestController
public class AuthenticationController {

    public static final String TOKEN_ENDPOINT = "/token";

    public static final String TOKEN_REFRESH_ENDPOINT = "/token-refresh";

    private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";

    private final AuthenticationService authenticationService;


    @Autowired
    public AuthenticationController(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    /**
     * Endpoint to generate a new authentication token.
     * <p>
     * This endpoint accepts a username and password, authenticates the user, and returns a token.
     *
     * @param username the username of the user
     * @param password the password of the user
     * @return a ResponseEntity containing the generated token details
     */
    @PostMapping(TOKEN_ENDPOINT)
    public ResponseEntity<TokenDetails> generateToken(@RequestParam String username,
                                                      @RequestParam String password) {
        final var tokenDetails = this.authenticationService.authenticate(username, password);
        final var refreshCookie = getResponseCookie(tokenDetails);
        return ResponseEntity
                .status(HttpStatus.CREATED)
                .header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
                .body(tokenDetails);
    }

    /**
     * Endpoint to refresh an existing authentication token.
     * <p>
     * This endpoint accepts a refresh token and returns a new access token.
     *
     * @param request the request containing the refresh token
     * @param refreshToken the refresh token from the cookie
     * @return a ResponseEntity containing the refreshed token details
     */
    @PostMapping(TOKEN_REFRESH_ENDPOINT)
    public ResponseEntity<TokenDetails> refreshToken(@RequestBody(required = false) TokenRefreshRequest request,
                                                     @CookieValue(REFRESH_TOKEN_COOKIE_NAME) String refreshToken) {
        final var tokenResponse = this.authenticationService.refreshAccessToken(Optional.ofNullable(refreshToken)
                .filter(StringUtils::hasLength)
                .orElse(Optional.ofNullable(request).map(TokenRefreshRequest::refreshToken).orElse("")));
        final var refreshCookie = getResponseCookie(tokenResponse);
        return ResponseEntity.status(HttpStatus.CREATED)
                .header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
                .body(tokenResponse);
    }

    /**
     * Creates a response cookie for the refresh token.
     * <p>
     * This method creates a secure, HTTP-only cookie for the refresh token.
     *
     * @param tokenDetails the token details containing the refresh token
     * @return a ResponseCookie for the refresh token
     */
    private ResponseCookie getResponseCookie(TokenDetails tokenDetails) {
        return ResponseCookie.from(REFRESH_TOKEN_COOKIE_NAME, tokenDetails.refreshToken())
                .httpOnly(true)
                .secure(true)
                .path(TOKEN_REFRESH_ENDPOINT)
                .build();
    }
}