Skip to content
IRC-Coding IRC-Coding
OAuth 2.0 Authorization Code Access Token Refresh Token Security Spring Security JWT OIDC

OAuth 2.0 Fundamentals: Authorization Code & Access Token

Learn OAuth 2.0 basics: Authorization Code flow, Access Token implementation, security best practices with Spring Security examples.

S

schutzgeist

2 min read
OAuth 2.0 Fundamentals: Authorization Code & Access Token

OAuth 2.0 Fundamentals: Authorization Code & Access Token

OAuth 2.0 is the industry standard for delegating authorization. It enables applications to access resources on behalf of a user without disclosing user credentials.

What is OAuth 2.0?

OAuth 2.0 is an authorization framework that allows third-party applications to obtain limited access to protected resources without exchanging user credentials.

Core Concepts of OAuth 2.0

  • Delegation: Users delegate access to applications
  • Token-based: Tokens are used instead of passwords
  • Scope-based: Access to defined scopes
  • Secure: Reduces attack surface through tokens with limited lifetime

OAuth 2.0 Roles

The Four Main Roles

// OAuth 2.0 Rollen als Java Klassen
public class OAuthRoles {
    
    // Resource Owner: Der Benutzer, der die Ressource besitzt
    public static class ResourceOwner {
        private String userId;
        private String username;
        private List<String> ownedResources;
        
        public ResourceOwner(String userId, String username) {
            this.userId = userId;
            this.username = username;
            this.ownedResources = new ArrayList<>();
        }
        
        public boolean ownsResource(String resourceId) {
            return ownedResources.contains(resourceId);
        }
        
        // Genehmigt oder lehnt Zugriff ab
        public boolean authorizeAccess(String clientId, List<String> scopes) {
            // Business Logik für die Genehmigung
            return true; // Vereinfacht
        }
    }
    
    // Resource Server: Hostet die geschützten Ressourcen
    public static class ResourceServer {
        private Map<String, ProtectedResource> resources;
        private TokenValidator tokenValidator;
        
        public ResourceServer() {
            this.resources = new HashMap<>();
            this.tokenValidator = new JWTTokenValidator();
        }
        
        public ProtectedResource getResource(String resourceId, String accessToken) {
            if (!tokenValidator.isValid(accessToken)) {
                throw new UnauthorizedException("Invalid token");
            }
            
            TokenInfo tokenInfo = tokenValidator.getTokenInfo(accessToken);
            if (!tokenInfo.hasScope("read")) {
                throw new ForbiddenException("Insufficient scope");
            }
            
            ProtectedResource resource = resources.get(resourceId);
            if (resource == null) {
                throw new NotFoundException("Resource not found");
            }
            
            return resource;
        }
    }
    
    // Authorization Server: Authentifiziert den Benutzer und stellt Tokens aus
    public static class AuthorizationServer {
        private ClientRegistry clientRegistry;
        private UserRegistry userRegistry;
        private TokenService tokenService;
        
        public AuthorizationServer() {
            this.clientRegistry = new ClientRegistry();
            this.userRegistry = new UserRegistry();
            this.tokenService = new JWTTokenService();
        }
        
        public AuthorizationCode generateAuthorizationCode(
                String clientId, String userId, List<String> scopes) {
            
            if (!clientRegistry.isValidClient(clientId)) {
                throw new InvalidClientException("Unknown client");
            }
            
            AuthorizationCode code = new AuthorizationCode(
                UUID.randomUUID().toString(),
                clientId,
                userId,
                scopes,
                Instant.now().plusSeconds(600) // 10 Minuten gültig
            );
            
            return code;
        }
        
        public TokenResponse exchangeCodeForTokens(String code, String clientId, String clientSecret) {
            AuthorizationCode authCode = validateAuthorizationCode(code, clientId);
            
            if (!clientRegistry.authenticateClient(clientId, clientSecret)) {
                throw new InvalidClientException("Authentication failed");
            }
            
            // Access Token und Refresh Token erstellen
            String accessToken = tokenService.createAccessToken(
                authCode.getUserId(), 
                authCode.getScopes()
            );
            
            String refreshToken = tokenService.createRefreshToken(
                authCode.getUserId()
            );
            
            return new TokenResponse(accessToken, refreshToken, 3600, "Bearer");
        }
        
        private AuthorizationCode validateAuthorizationCode(String code, String clientId) {
            // Implementierung zur Validierung des Authorization Codes
            return new AuthorizationCode(code, clientId, "user123", 
                Arrays.asList("read", "write"), Instant.now());
        }
    }
    
    // Client: Die Anwendung, die Zugriff anfordert
    public static class Client {
        private String clientId;
        private String clientSecret;
        private List<String> redirectUris;
        private List<String> allowedScopes;
        
        public Client(String clientId, String clientSecret) {
            this.clientId = clientId;
            this.clientSecret = clientSecret;
            this.redirectUris = new ArrayList<>();
            this.allowedScopes = Arrays.asList("read", "write", "profile");
        }
        
        public String initiateAuthorizationFlow(List<String> requestedScopes) {
            // Authorization Request erstellen
            String authUrl = String.format(
                "https://auth.example.com/authorize?" +
                "response_type=code&" +
                "client_id=%s&" +
                "redirect_uri=%s&" +
                "scope=%s&" +
                "state=%s",
                clientId,
                "https://client.example.com/callback",
                String.join(" ", requestedScopes),
                UUID.randomUUID().toString()
            );
            
            return authUrl;
        }
        
        public TokenResponse exchangeCodeForTokens(String code) {
            // Token Request an Authorization Server senden
            return tokenService.exchangeCode(code, clientId, clientSecret);
        }
    }
}

Authorization Code Flow

The Most Secure OAuth 2.0 Flow

public class AuthorizationCodeFlow {
    
    // Schritt 1: Authorization Request
    public String buildAuthorizationRequest() {
        StringBuilder request = new StringBuilder("https://auth.example.com/authorize?");
        request.append("response_type=code");
        request.append("&client_id=client123");
        request.append("&redirect_uri=https://client.example.com/callback");
        request.append("&scope=read%20write%20profile");
        request.append("&state=xyz123"); // CSRF Protection
        
        return request.toString();
    }
    
    // Schritt 2: User Authorization
    public void handleUserAuthorization(String userId, String clientId, List<String> scopes) {
        // Benutzer wird zur Anmeldeseite weitergeleitet
        // Nach erfolgreicher Authentifizierung:
        
        if (userConsentsToScopes(userId, scopes)) {
            AuthorizationCode code = authorizationServer.generateAuthorizationCode(
                clientId, userId, scopes
            );
            
            // Redirect mit Authorization Code
            String redirectUri = String.format(
                "https://client.example.com/callback?code=%s&state=xyz123",
                code.getValue()
            );
            
            redirectToClient(redirectUri);
        } else {
            // Benutzer hat abgelehnt
            redirectToClient("https://client.example.com/callback?error=access_denied");
        }
    }
    
    // Schritt 3: Token Exchange
    public TokenResponse exchangeCodeForTokens(String code, String state) {
        // State validieren (CSRF Protection)
        if (!isValidState(state)) {
            throw new SecurityException("Invalid state parameter");
        }
        
        // Token Request an Authorization Server
        Map<String, String> tokenRequest = new HashMap<>();
        tokenRequest.put("grant_type", "authorization_code");
        tokenRequest.put("code", code);
        tokenRequest.put("redirect_uri", "https://client.example.com/callback");
        tokenRequest.put("client_id", "client123");
        tokenRequest.put("client_secret", "secret123");
        
        // HTTP POST Request senden
        TokenResponse response = httpClient.postForm(
            "https://auth.example.com/token", 
            tokenRequest
        );
        
        return response;
    }
    
    // Schritt 4: Resource Access
    public ProtectedResource accessResource(String accessToken, String resourceId) {
        // Access Token im Authorization Header senden
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + accessToken);
        
        return httpClient.get(
            "https://api.example.com/resources/" + resourceId,
            headers
        );
    }
}

Spring Security OAuth 2.0 Implementation

@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/private/**").authenticated()
                .anyRequest().denyAll()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/oauth2/authorization/my-client")
                .authorizationEndpoint(authorization -> authorization
                    .baseUri("/oauth2/authorize")
                )
                .redirectionEndpoint(redirection -> redirection
                    .baseUri("/oauth2/callback/*")
                )
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(customOAuth2UserService())
                )
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .decoder(jwtDecoder())
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            );
        
        return http.build();
    }
    
    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/.well-known/jwks.json")
            .build();
    }
    
    @Bean
    public Converter<Jwt, UsernamePasswordAuthenticationToken> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
        authoritiesConverter.setAuthorityPrefix("ROLE_");
        authoritiesConverter.setAuthoritiesClaimName("roles");
        
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
        return converter;
    }
    
    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
        return new CustomOAuth2UserService();
    }
}

// Custom OAuth2 User Service
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = 
            new DefaultOAuth2UserService();
        
        OAuth2User oAuth2User = delegate.loadUser(userRequest);
        
        // Process user information and save in database
        String provider = userRequest.getClientRegistration().getRegistrationId();
        String providerId = oAuth2User.getAttribute("id");
        
        User user = findOrCreateUser(provider, providerId, oAuth2User);
        
        return new CustomOAuth2User(user, oAuth2User.getAttributes());
    }
    
    private User findOrCreateUser(String provider, String providerId, OAuth2User oAuth2User) {
        // Implementation for user creation/search
        return userRepository.findByProviderAndProviderId(provider, providerId)
            .orElseGet(() -> createUser(provider, providerId, oAuth2User));
    }
    
    private User createUser(String provider, String providerId, OAuth2User oAuth2User) {
        User user = new User();
        user.setProvider(provider);
        user.setProviderId(providerId);
        user.setEmail(oAuth2User.getAttribute("email"));
        user.setName(oAuth2User.getAttribute("name"));
        user.setRoles(Arrays.asList("USER"));
        
        return userRepository.save(user);
    }
}

Token Management

JWT Token Implementation

@Component
public class JWTTokenService {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private int jwtExpiration;
    
    @Value("${jwt.refresh-expiration}")
    private int refreshExpiration;
    
    public String createAccessToken(String userId, List<String> scopes) {
        return createToken(userId, scopes, jwtExpiration);
    }
    
    public String createRefreshToken(String userId) {
        return createToken(userId, Arrays.asList("refresh"), refreshExpiration);
    }
    
    private String createToken(String userId, List<String> scopes, int expiration) {
        Instant now = Instant.now();
        Instant expiry = now.plusSeconds(expiration);
        
        return Jwts.builder()
            .setSubject(userId)
            .claim("scopes", scopes)
            .claim("type", scopes.contains("refresh") ? "refresh" : "access")
            .setIssuedAt(Date.from(now))
            .setExpiration(Date.from(expiry))
            .signWith(SignatureAlgorithm.HS256, jwtSecret)
            .compact();
    }
    
    public Claims validateToken(String token) {
        try {
            return Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();
        } catch (ExpiredJwtException e) {
            throw new TokenExpiredException("Token expired");
        } catch (JwtException e) {
            throw new InvalidTokenException("Invalid token");
        }
    }
    
    public String refreshToken(String refreshToken) {
        Claims claims = validateToken(refreshToken);
        
        if (!"refresh".equals(claims.get("type"))) {
            throw new InvalidTokenException("Not a refresh token");
        }
        
        String userId = claims.getSubject();
        List<String> scopes = Arrays.asList("read", "write"); // Default scopes
        
        return createAccessToken(userId, scopes);
    }
}

// Token Controller
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    
    @Autowired
    private JWTTokenService tokenService;
    
    @PostMapping("/token")
    public ResponseEntity<TokenResponse> exchangeCodeForToken(
            @RequestBody TokenRequest tokenRequest) {
        
        try {
            // Validate authorization code
            AuthorizationCode code = validateAuthorizationCode(tokenRequest.getCode());
            
            // Create tokens
            String accessToken = tokenService.createAccessToken(
                code.getUserId(), 
                code.getScopes()
            );
            
            String refreshToken = tokenService.createRefreshToken(code.getUserId());
            
            TokenResponse response = new TokenResponse(
                accessToken, 
                refreshToken, 
                3600, 
                "Bearer"
            );
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            return ResponseEntity.badRequest()
                .body(new ErrorResponse("invalid_grant", e.getMessage()));
        }
    }
    
    @PostMapping("/refresh")
    public ResponseEntity<TokenResponse> refreshToken(
            @RequestBody RefreshTokenRequest request) {
        
        try {
            String newAccessToken = tokenService.refreshToken(request.getRefreshToken());
            
            TokenResponse response = new TokenResponse(
                newAccessToken, 
                request.getRefreshToken(), 
                3600, 
                "Bearer"
            );
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            return ResponseEntity.badRequest()
                .body(new ErrorResponse("invalid_grant", e.getMessage()));
        }
    }
    
    @PostMapping("/revoke")
    public ResponseEntity<Void> revokeToken(@RequestBody RevokeTokenRequest request) {
        // Add token to blacklist
        tokenBlacklist.addToBlacklist(request.getToken());
        return ResponseEntity.ok().build();
    }
}

Token Blacklist

@Service
public class TokenBlacklist {
    
    private final RedisTemplate<String, String> redisTemplate;
    private static final String BLACKLIST_PREFIX = "blacklist:";
    
    public TokenBlacklist(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    public void addToBlacklist(String token) {
        String jti = extractJti(token);
        long expiration = extractExpiration(token);
        
        redisTemplate.opsForValue().set(
            BLACKLIST_PREFIX + jti, 
            "revoked", 
            Duration.ofSeconds(expiration - System.currentTimeMillis() / 1000)
        );
    }
    
    public boolean isBlacklisted(String token) {
        String jti = extractJti(token);
        return Boolean.TRUE.equals(redisTemplate.hasKey(BLACKLIST_PREFIX + jti));
    }
    
    private String extractJti(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
        
        return claims.getId();
    }
    
    private long extractExpiration(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
        
        return claims.getExpiration().getTime() / 1000;
    }
}

OAuth 2.0 Security Best Practices

1. State Parameter (CSRF Protection)

public class StateManager {
    
    public String generateState() {
        return UUID.randomUUID().toString();
    }
    
    public void storeState(String state, String sessionId) {
        // Store state in session or Redis
        redisTemplate.opsForValue().set(
            "oauth2_state:" + state, 
            sessionId, 
            Duration.ofMinutes(10)
        );
    }
    
    public boolean validateState(String state, String sessionId) {
        String storedSessionId = redisTemplate.opsForValue()
            .get("oauth2_state:" + state);
        
        if (storedSessionId == null) {
            return false; // State expired or not found
        }
        
        if (!storedSessionId.equals(sessionId)) {
            return false; // State does not match
        }
        
        // Delete state after use
        redisTemplate.delete("oauth2_state:" + state);
        return true;
    }
}

2. PKCE (Proof Key for Code Exchange)

public class PKCEManager {
    
    public PKCEPair generatePKCEPair() {
        String codeVerifier = generateCodeVerifier();
        String codeChallenge = generateCodeChallenge(codeVerifier);
        
        return new PKCEPair(codeVerifier, codeChallenge);
    }
    
    private String generateCodeVerifier() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[32];
        random.nextBytes(bytes);
        
        return Base64.getUrlEncoder()
            .withoutPadding()
            .encodeToString(bytes);
    }
    
    private String generateCodeChallenge(String codeVerifier) {
        byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII);
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(bytes);
        
        return Base64.getUrlEncoder()
            .withoutPadding()
            .encodeToString(digest);
    }
    
    public boolean verifyCodeChallenge(String codeVerifier, String codeChallenge) {
        String computedChallenge = generateCodeChallenge(codeVerifier);
        return computedChallenge.equals(codeChallenge);
    }
}

3. Secure Token Storage

@Component
public class SecureTokenStorage {
    
    @Value("${token.encryption.key}")
    private String encryptionKey;
    
    public void storeTokens(String sessionId, TokenResponse tokens) {
        // Store tokens encrypted
        String encryptedAccessToken = encrypt(tokens.getAccessToken());
        String encryptedRefreshToken = encrypt(tokens.getRefreshToken());
        
        TokenStorage storage = new TokenStorage(
            encryptedAccessToken,
            encryptedRefreshToken,
            Instant.now().plusSeconds(tokens.getExpiresIn())
        );
        
        redisTemplate.opsForValue().set(
            "tokens:" + sessionId,
            storage,
            Duration.ofDays(30)
        );
    }
    
    public TokenResponse getTokens(String sessionId) {
        TokenStorage storage = redisTemplate.opsForValue()
            .get("tokens:" + sessionId);
        
        if (storage == null) {
            return null;
        }
        
        String accessToken = decrypt(storage.getAccessToken());
        String refreshToken = decrypt(storage.getRefreshToken());
        
        return new TokenResponse(
            accessToken,
            refreshToken,
            storage.getExpiresIn(),
            "Bearer"
        );
    }
    
    private String encrypt(String data) {
        // Implementation with AES
        return AESTextEncryption.encrypt(data, encryptionKey);
    }
    
    private String decrypt(String encryptedData) {
        // Implementation with AES
        return AESTextEncryption.decrypt(encryptedData, encryptionKey);
    }
}

OpenID Connect (OIDC)

OIDC Integration

@Configuration
public class OIDCConfig {
    
    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {
        
        OAuth2AuthorizedClientProvider authorizedClientProvider = 
            OAuth2AuthorizedClientProviderBuilder.builder()
                .authorizationCode()
                .refreshToken()
                .build();
        
        DefaultOAuth2AuthorizedClientManager authorizedClientManager = 
            new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, 
                authorizedClientRepository);
        
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        
        return authorizedClientManager;
    }
}

// OIDC User Info Service
@Service
public class OIDCUserInfoService {
    
    public OIDCUserInfo getUserInfo(String accessToken) {
        // Call User Info Endpoint
        String userInfoUrl = "https://auth.example.com/oauth2/userinfo";
        
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);
        
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        ResponseEntity<OIDCUserInfo> response = restTemplate.exchange(
            userInfoUrl,
            HttpMethod.GET,
            entity,
            OIDCUserInfo.class
        );
        
        return response.getBody();
    }
}

// OIDC User Info DTO
public class OIDCUserInfo {
    private String sub;
    private String name;
    private String email;
    private String picture;
    private List<String> roles;
    
    // Getter and Setter
    public String getSubject() { return sub; }
    public String getName() { return name; }
    public String getEmail() { return email; }
    public String getPicture() { return picture; }
    public List<String> getRoles() { return roles; }
}

OAuth 2.0 Client Implementation

JavaScript Client Example

class OAuth2Client {
    constructor(config) {
        this.clientId = config.clientId;
        this.redirectUri = config.redirectUri;
        this.authUrl = config.authUrl;
        this.tokenUrl = config.tokenUrl;
        this.scopes = config.scopes;
    }
    
    // Authorization Request initiieren
    initiateAuthorization() {
        const state = this.generateState();
        const codeVerifier = this.generateCodeVerifier();
        const codeChallenge = this.generateCodeChallenge(codeVerifier);
        
        // State und Code Verifier speichern
        sessionStorage.setItem('oauth2_state', state);
        sessionStorage.setItem('oauth2_code_verifier', codeVerifier);
        
        const authUrl = new URL(this.authUrl);
        authUrl.searchParams.set('response_type', 'code');
        authUrl.searchParams.set('client_id', this.clientId);
        authUrl.searchParams.set('redirect_uri', this.redirectUri);
        authUrl.searchParams.set('scope', this.scopes.join(' '));
        authUrl.searchParams.set('state', state);
        authUrl.searchParams.set('code_challenge', codeChallenge);
        authUrl.searchParams.set('code_challenge_method', 'S256');
        
        // Weiterleitung zur Authorization Server
        window.location.href = authUrl.toString();
    }
    
    // Callback verarbeiten
    async handleCallback() {
        const urlParams = new URLSearchParams(window.location.search);
        const code = urlParams.get('code');
        const state = urlParams.get('state');
        const storedState = sessionStorage.getItem('oauth2_state');
        
        // State validieren
        if (state !== storedState) {
            throw new Error('Invalid state parameter');
        }
        
        // Code gegen Tokens eintauschen
        const codeVerifier = sessionStorage.getItem('oauth2_code_verifier');
        const tokenResponse = await this.exchangeCodeForTokens(code, codeVerifier);
        
        // Tokens speichern
        localStorage.setItem('access_token', tokenResponse.access_token);
        localStorage.setItem('refresh_token', tokenResponse.refresh_token);
        
        // Cleanup
        sessionStorage.removeItem('oauth2_state');
        sessionStorage.removeItem('oauth2_code_verifier');
        
        return tokenResponse;
    }
    
    // Token Exchange
    async exchangeCodeForTokens(code, codeVerifier) {
        const response = await fetch(this.tokenUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                code: code,
                redirect_uri: this.redirectUri,
                client_id: this.clientId,
                code_verifier: codeVerifier
            })
        });
        
        if (!response.ok) {
            throw new Error('Token exchange failed');
        }
        
        return await response.json();
    }
    
    // Access Token erneuern
    async refreshAccessToken() {
        const refreshToken = localStorage.getItem('refresh_token');
        
        const response = await fetch(this.tokenUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
                grant_type: 'refresh_token',
                refresh_token: refreshToken,
                client_id: this.clientId
            })
        });
        
        if (!response.ok) {
            throw new Error('Token refresh failed');
        }
        
        const tokenResponse = await response.json();
        localStorage.setItem('access_token', tokenResponse.access_token);
        
        return tokenResponse;
    }
    
    // API Request mit Access Token
    async makeAuthenticatedRequest(url, options = {}) {
        let accessToken = localStorage.getItem('access_token');
        
        // Token prüfen und ggf. erneuern
        if (this.isTokenExpired(accessToken)) {
            await this.refreshAccessToken();
            accessToken = localStorage.getItem('access_token');
        }
        
        const headers = {
            'Authorization': `Bearer ${accessToken}`,
            ...options.headers
        };
        
        return fetch(url, {
            ...options,
            headers
        });
    }
    
    // Hilfsmethoden
    generateState() {
        return Math.random().toString(36).substring(2, 15);
    }
    
    generateCodeVerifier() {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return btoa(String.fromCharCode.apply(null, array))
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
    }
    
    async generateCodeChallenge(verifier) {
        const encoder = new TextEncoder();
        const data = encoder.encode(verifier);
        const digest = await crypto.subtle.digest('SHA-256', data);
        return btoa(String.fromCharCode.apply(null, new Uint8Array(digest)))
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
    }
    
    isTokenExpired(token) {
        try {
            const payload = JSON.parse(atob(token.split('.')[1]));
            return Date.now() >= payload.exp * 1000;
        } catch {
            return true;
        }
    }
}

// Verwendung
const oauth2Client = new OAuth2Client({
    clientId: 'your-client-id',
    redirectUri: 'http://localhost:3000/callback',
    authUrl: 'https://auth.example.com/oauth2/authorize',
    tokenUrl: 'https://auth.example.com/oauth2/token',
    scopes: ['read', 'write', 'profile']
});

// Login initiieren
document.getElementById('login-btn').addEventListener('click', () => {
    oauth2Client.initiateAuthorization();
});

// API Request
document.getElementById('fetch-data-btn').addEventListener('click', async () => {
    try {
        const response = await oauth2Client.makeAuthenticatedRequest(
            'https://api.example.com/user/profile'
        );
        const data = await response.json();
        console.log('User data:', data);
    } catch (error) {
        console.error('API request failed:', error);
    }
});

Exam-Relevant Concepts

Important OAuth 2.0 Flows

  1. Authorization Code Flow: Safest method for web apps
  2. Implicit Flow: Deprecated, no longer recommended
  3. Client Credentials Flow: For machine-to-machine communication
  4. Resource Owner Password Credentials: Only for trusted applications

Security Aspects

  • State Parameter: CSRF protection
  • PKCE: Protection against code interception
  • Token Storage: Secure storage of tokens
  • HTTPS: Encrypted communication required
  • Scope Limitation: Request minimal permissions

Typical Exam Tasks

  1. Explain the Authorization Code Flow
  2. Compare OAuth 2.0 with OpenID Connect
  3. Implement a simple OAuth 2.0 client
  4. Describe security risks and countermeasures

Summary

OAuth 2.0 is a powerful framework for delegating authorization:

  • Secure: Token-based authentication instead of passwords
  • Flexible: Different flows for different use cases
  • Standardized: Widely adopted and well-supported
  • Extensible: OpenID Connect for identity management

Proper implementation requires careful attention to security aspects such as state parameters, PKCE, and secure token storage.

Back to Blog
Share:

Related Posts