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

OAuth 2.0 Grundlagen: Authorization Code & Access Token

OAuth 2.0 Grundlagen mit Authorization Code Flows, Access Token Implementierung und Security Best Practices. Spring Security Beispiel.

S

schutzgeist

2 min read

OAuth 2.0 Grundlagen: Authorization Code & Access Token

OAuth 2.0 ist der Industriestandard für die Delegierung von Autorisierung. Es ermöglicht Anwendungen, im Namen eines Benutzers auf Ressourcen zuzugreifen, ohne die Benutzerkennungen preiszugeben.

Was ist OAuth 2.0?

OAuth 2.0 ist ein Framework für die Autorisierung, das es Drittanwendungen ermöglicht, begrenzten Zugriff auf geschützte Ressourcen zu erhalten, ohne Benutzerkennungen auszutauschen.

Kernkonzepte von OAuth 2.0

  • Delegierung: Benutzer delegieren Zugriff an Anwendungen
  • Token-basiert: Statt Passwörtern werden Tokens verwendet
  • Bereichsbasiert: Zugriff auf definierte Bereiche (Scopes)
  • Sicher: Reduziert Angriffsfläche durch Token mit begrenzter Lebensdauer

OAuth 2.0 Rollen

Die vier Hauptrollen

// 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

Der sicherste 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 Implementierung

@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);
        
        // Benutzerinformationen verarbeiten und in Datenbank speichern
        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) {
        // Implementierung zur Benutzererstellung/-suche
        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 Implementierung

@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 {
            // Authorization Code validieren
            AuthorizationCode code = validateAuthorizationCode(tokenRequest.getCode());
            
            // Tokens erstellen
            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) {
        // Token auf die Blacklist setzen
        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) {
        // State in Session oder Redis speichern
        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 abgelaufen oder nicht gefunden
        }
        
        if (!storedSessionId.equals(sessionId)) {
            return false; // State stimmt nicht überein
        }
        
        // State nach Verwendung löschen
        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) {
        // Tokens verschlüsseln speichern
        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) {
        // Implementierung mit AES
        return AESTextEncryption.encrypt(data, encryptionKey);
    }
    
    private String decrypt(String encryptedData) {
        // Implementierung mit 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) {
        // User Info Endpoint aufrufen
        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 und 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 Implementierung

JavaScript Client Beispiel

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);
    }
});

Prüfungsrelevante Konzepte

Wichtige OAuth 2.0 Flows

  1. Authorization Code Flow: Sicherste Methode für Web-Apps
  2. Implicit Flow: Veraltet, nicht mehr empfohlen
  3. Client Credentials Flow: Für Machine-to-Machine Kommunikation
  4. Resource Owner Password Credentials: Nur für vertrauenswürdige Anwendungen

Sicherheitsaspekte

  • State Parameter: CSRF Protection
  • PKCE: Schutz gegen Code Interception
  • Token Storage: Sichere Speicherung von Tokens
  • HTTPS: Verschlüsselte Kommunikation erforderlich
  • Scope Limitation: Minimale Berechtigungen anfordern

Typische Prüfungsaufgaben

  1. Erklären Sie den Authorization Code Flow
  2. Vergleichen Sie OAuth 2.0 mit OpenID Connect
  3. Implementieren Sie einen einfachen OAuth 2.0 Client
  4. Beschreiben Sie Sicherheitsrisiken und Gegenmaßnahmen

Zusammenfassung

OAuth 2.0 ist ein mächtiges Framework für die Delegierung von Autorisierung:

  • Sicher: Token-basierte Authentifizierung statt Passwörter
  • Flexibel: Verschiedene Flows für unterschiedliche Anwendungsfälle
  • Standardisiert: Weit verbreitet und gut unterstützt
  • Erweiterbar: OpenID Connect für Identitätsmanagement

Die richtige Implementierung erfordert sorgfältige Beachtung von Sicherheitsaspekten wie State Parameters, PKCE und sicherer Token Storage.

Zurück zum Blog
Share:

Ähnliche Beiträge