Skip to content

Authentication & Authorization Overview


Authentication vs Authorization

Authentication vs Authorization


Authentication Methods

Authentication Methods


Session-Based Authentication

Session-Based Authentication

Session Implementation

// Spring Session with Redis
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)  // 30 minutes
public class SessionConfig {

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory(
            new RedisStandaloneConfiguration("localhost", 6379));
    }
}

// Session usage in controller
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request,
                                HttpSession session) {
    User user = authService.authenticate(request);
    session.setAttribute("userId", user.getId());
    session.setAttribute("roles", user.getRoles());
    return ResponseEntity.ok().build();
}

@GetMapping("/profile")
public ResponseEntity<User> getProfile(HttpSession session) {
    Long userId = (Long) session.getAttribute("userId");
    if (userId == null) {
        throw new UnauthorizedException("Not authenticated");
    }
    return ResponseEntity.ok(userService.findById(userId));
}

Token-Based Authentication (JWT)

Token-Based Authentication

JWT Structure

JWT Structure

JWT Implementation

@Service
public class JwtService {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration:3600000}")  // 1 hour default
    private long expiration;

    public String generateToken(User user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", user.getRoles());

        return Jwts.builder()
            .setClaims(claims)
            .setSubject(user.getId().toString())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();
    }

    public Claims validateToken(String token) {
        return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
    }

    public String extractUserId(String token) {
        return validateToken(token).getSubject();
    }
}

// JWT Filter
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                     HttpServletResponse response,
                                     FilterChain chain) throws ServletException, IOException {
        String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            try {
                Claims claims = jwtService.validateToken(token);

                List<String> roles = claims.get("roles", List.class);
                List<GrantedAuthority> authorities = roles.stream()
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());

                UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(
                        claims.getSubject(), null, authorities);
                SecurityContextHolder.getContext().setAuthentication(auth);
            } catch (JwtException e) {
                // Invalid token - don't set authentication
            }
        }

        chain.doFilter(request, response);
    }
}

Access Token + Refresh Token Pattern

Access Token + Refresh Token Pattern

@Service
public class TokenService {

    private static final long ACCESS_TOKEN_VALIDITY = 15 * 60 * 1000;  // 15 min
    private static final long REFRESH_TOKEN_VALIDITY = 7 * 24 * 60 * 60 * 1000;  // 7 days

    @Autowired
    private RefreshTokenRepository refreshTokenRepository;

    public TokenPair createTokenPair(User user) {
        String accessToken = generateAccessToken(user);
        String refreshToken = generateRefreshToken(user);

        // Store refresh token in database (for revocation)
        RefreshToken rt = new RefreshToken();
        rt.setToken(refreshToken);
        rt.setUserId(user.getId());
        rt.setExpiryDate(Instant.now().plusMillis(REFRESH_TOKEN_VALIDITY));
        refreshTokenRepository.save(rt);

        return new TokenPair(accessToken, refreshToken);
    }

    public TokenPair refreshAccessToken(String refreshToken) {
        RefreshToken stored = refreshTokenRepository.findByToken(refreshToken)
            .orElseThrow(() -> new InvalidTokenException("Invalid refresh token"));

        if (stored.getExpiryDate().isBefore(Instant.now())) {
            refreshTokenRepository.delete(stored);
            throw new InvalidTokenException("Refresh token expired");
        }

        User user = userRepository.findById(stored.getUserId()).orElseThrow();

        // Rotate refresh token (optional but recommended)
        refreshTokenRepository.delete(stored);

        return createTokenPair(user);
    }

    public void revokeRefreshToken(String refreshToken) {
        refreshTokenRepository.deleteByToken(refreshToken);
    }

    public void revokeAllUserTokens(Long userId) {
        refreshTokenRepository.deleteByUserId(userId);
    }
}

Authorization Models

Role-Based Access Control (RBAC)

Role-Based Access Control (RBAC)

// Spring Security RBAC
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
    userService.delete(id);
}

@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
@GetMapping("/reports")
public List<Report> getReports() {
    return reportService.findAll();
}

// Permission-based
@PreAuthorize("hasAuthority('user:delete')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable Long id) {
    userService.delete(id);
}

Attribute-Based Access Control (ABAC)

Attribute-Based Access Control (ABAC)

// ABAC-style authorization
@PreAuthorize("@documentAuthz.canEdit(#documentId, authentication)")
@PutMapping("/documents/{documentId}")
public Document updateDocument(@PathVariable Long documentId,
                                @RequestBody Document document) {
    return documentService.update(documentId, document);
}

@Service("documentAuthz")
public class DocumentAuthorization {

    public boolean canEdit(Long documentId, Authentication auth) {
        Document doc = documentRepository.findById(documentId).orElse(null);
        if (doc == null) return false;

        User user = (User) auth.getPrincipal();

        // Check multiple attributes
        return doc.getOwnerId().equals(user.getId()) ||
               (doc.getDepartment().equals(user.getDepartment()) &&
                doc.getStatus().equals("DRAFT")) ||
               user.getRoles().contains("ADMIN");
    }
}

Relationship-Based Access Control (ReBAC)

Relationship-Based Access Control (ReBAC)


API Key Authentication

API Key Authentication

@Service
public class ApiKeyService {

    public ApiKey createApiKey(Long userId, String name, Set<String> scopes) {
        String rawKey = generateSecureKey();
        String hashedKey = hashKey(rawKey);

        ApiKey apiKey = new ApiKey();
        apiKey.setKeyHash(hashedKey);
        apiKey.setKeyPrefix(rawKey.substring(0, 8));  // For identification
        apiKey.setUserId(userId);
        apiKey.setName(name);
        apiKey.setScopes(scopes);
        apiKey.setCreatedAt(Instant.now());

        apiKeyRepository.save(apiKey);

        // Return raw key only once - user must save it
        apiKey.setRawKey(rawKey);
        return apiKey;
    }

    public Optional<ApiKey> validateKey(String rawKey) {
        String hashedKey = hashKey(rawKey);
        return apiKeyRepository.findByKeyHash(hashedKey)
            .filter(key -> !key.isRevoked())
            .filter(key -> key.getExpiresAt() == null ||
                          key.getExpiresAt().isAfter(Instant.now()));
    }

    private String generateSecureKey() {
        byte[] bytes = new byte[32];
        new SecureRandom().nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    private String hashKey(String rawKey) {
        return DigestUtils.sha256Hex(rawKey);
    }
}

Single Sign-On (SSO)

Single Sign-On (SSO)


Multi-Factor Authentication (MFA)

MFA Types

TOTP Implementation

// Using aerogear-otp-java or similar library
@Service
public class TotpService {

    private static final int SECRET_SIZE = 20;

    public String generateSecret() {
        byte[] buffer = new byte[SECRET_SIZE];
        new SecureRandom().nextBytes(buffer);
        return new Base32().encodeToString(buffer);
    }

    public String getQrCodeUri(String secret, String email, String issuer) {
        return String.format(
            "otpauth://totp/%s:%s?secret=%s&issuer=%s&algorithm=SHA1&digits=6&period=30",
            URLEncoder.encode(issuer, StandardCharsets.UTF_8),
            URLEncoder.encode(email, StandardCharsets.UTF_8),
            secret,
            URLEncoder.encode(issuer, StandardCharsets.UTF_8)
        );
    }

    public boolean verifyCode(String secret, String code) {
        Totp totp = new Totp(secret);
        return totp.verify(code);
    }
}

// MFA-enabled login flow
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
    User user = authService.validateCredentials(request);

    if (user.isMfaEnabled()) {
        // Return partial token, require MFA verification
        String partialToken = jwtService.generatePartialToken(user);
        return ResponseEntity.ok(new MfaRequiredResponse(partialToken));
    }

    return ResponseEntity.ok(jwtService.generateFullToken(user));
}

@PostMapping("/login/mfa")
public ResponseEntity<?> verifyMfa(@RequestBody MfaRequest request) {
    Claims claims = jwtService.validatePartialToken(request.getPartialToken());
    User user = userService.findById(claims.getSubject());

    if (!totpService.verifyCode(user.getMfaSecret(), request.getCode())) {
        throw new InvalidMfaCodeException("Invalid MFA code");
    }

    return ResponseEntity.ok(jwtService.generateFullToken(user));
}

Security Best Practices

Security Best Practices


Common Interview Questions

  1. Session vs JWT?
  2. Session: Stateful, server stores data, easy to invalidate
  3. JWT: Stateless, self-contained, scalable, hard to revoke

  4. How to handle token revocation?

  5. Short-lived access tokens + refresh tokens
  6. Blacklist for immediate revocation (Redis)
  7. Token versioning per user

  8. RBAC vs ABAC?

  9. RBAC: Simple, role-based, coarse-grained
  10. ABAC: Complex, attribute-based, fine-grained

  11. How to secure password storage?

  12. Use bcrypt/Argon2 with appropriate work factor
  13. Never store plain text or weak hashes
  14. Add pepper (application-level secret)

  15. What is OAuth 2.0 vs OIDC?

  16. OAuth 2.0: Authorization framework (access tokens)
  17. OIDC: Authentication layer on OAuth 2.0 (ID tokens)

  • *