Skip to content

Commit 5d1cc54

Browse files
committed
Updated Examples file
1 parent 5cf4798 commit 5d1cc54

3 files changed

Lines changed: 115 additions & 100 deletions

File tree

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.auth0.playground;
22

3+
import org.springframework.http.ResponseEntity;
34
import org.springframework.security.core.Authentication;
45
import org.springframework.web.bind.annotation.GetMapping;
56
import org.springframework.web.bind.annotation.RequestMapping;
@@ -11,13 +12,20 @@
1112
public class ProfileController {
1213

1314
@GetMapping("/protected")
14-
public String protectedEndpoint(Authentication authentication) {
15-
System.out.println("🔐 Received request for protected resource: "+ authentication.getPrincipal().toString());
16-
return "Hello " + authentication.getName() + ", access granted!";
15+
public ResponseEntity<Map<String, Object>> protectedEndpoint(Authentication authentication) {
16+
String userId = authentication.getName(); // Returns the 'sub' claim
17+
18+
return ResponseEntity.ok(Map.of(
19+
"message", "Access granted!",
20+
"user", userId,
21+
"authenticated", true
22+
));
1723
}
1824

1925
@GetMapping("/public")
20-
public Map<String, Object> pub() {
21-
return Map.of("message", "Public endpoint — no token required");
26+
public ResponseEntity<Map<String, Object>> publicEndpoint() {
27+
return ResponseEntity.ok(Map.of(
28+
"message", "Public endpoint - no token required"
29+
));
2230
}
2331
}

auth0-springboot-api-playground/src/main/java/com/auth0/playground/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
import com.auth0.spring.boot.Auth0AuthenticationFilter;
44
import org.springframework.context.annotation.Bean;
55
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
67
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
78
import org.springframework.security.config.http.SessionCreationPolicy;
89
import org.springframework.security.web.SecurityFilterChain;
910
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1011

1112
@Configuration
13+
@EnableMethodSecurity
1214
public class SecurityConfig {
1315
@Bean
1416
SecurityFilterChain apiSecurity(

auth0-springboot-api/EXAMPLES.md

Lines changed: 100 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public class SecurityConfig {
4040
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
4141
)
4242
.authorizeHttpRequests(auth -> auth
43-
.requestMatchers("/api/protected").authenticated()
4443
.requestMatchers("/api/public").permitAll()
44+
.requestMatchers("/api/protected").authenticated()
4545
.anyRequest().authenticated()
4646
)
4747
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
@@ -86,16 +86,12 @@ public class UserController {
8686
8787
@GetMapping("/profile")
8888
public ResponseEntity<Map<String, Object>> getUserProfile(Authentication authentication) {
89-
if (authentication instanceof Auth0AuthenticationToken) {
90-
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
91-
DecodedJWT jwt = auth0Token.getJwt();
92-
89+
if (authentication instanceof Auth0AuthenticationToken auth0Token) {
9390
return ResponseEntity.ok(Map.of(
94-
"sub", jwt.getSubject(),
95-
"email", jwt.getClaim("email").asString(),
96-
"scope", jwt.getClaim("scope").asString(),
97-
"exp", jwt.getExpiresAt(),
98-
"iat", jwt.getIssuedAt()
91+
"sub", String.valueOf(auth0Token.getClaim("sub")),
92+
"email", String.valueOf(auth0Token.getClaim("email")),
93+
"scope", String.valueOf(auth0Token.getClaim("scope")),
94+
"scopes", auth0Token.getScopes()
9995
));
10096
}
10197
@@ -118,7 +114,7 @@ Accepts both Bearer and DPoP tokens:
118114
auth0:
119115
domain: "your-tenant.auth0.com"
120116
audience: "https://api.example.com"
121-
dpopMode: ALLOWED # Default value
117+
dpop-mode: ALLOWED
122118
```
123119

124120
#### 2. Required Mode
@@ -129,7 +125,7 @@ Only accepts DPoP tokens:
129125
auth0:
130126
domain: "your-tenant.auth0.com"
131127
audience: "https://api.example.com"
132-
dpopMode: REQUIRED
128+
dpop-mode: REQUIRED
133129
```
134130

135131
#### 3. Disabled Mode
@@ -140,7 +136,7 @@ Only accepts Bearer tokens:
140136
auth0:
141137
domain: "your-tenant.auth0.com"
142138
audience: "https://api.example.com"
143-
dpopMode: DISABLED
139+
dpop-mode: DISABLED
144140
```
145141

146142
### Advanced DPoP Configuration
@@ -149,120 +145,137 @@ auth0:
149145
auth0:
150146
domain: "your-tenant.auth0.com"
151147
audience: "https://api.example.com"
152-
dpopMode: ALLOWED
153-
dpopIatOffsetSeconds: 300 # DPoP proof time window (default: 300)
154-
dpopIatLeewaySeconds: 30 # DPoP proof time leeway (default: 30)
148+
dpop-mode: ALLOWED
149+
dpop-iat-offset-seconds: 300 # DPoP proof time window (default: 300)
150+
dpop-iat-leeway-seconds: 30 # DPoP proof time leeway (default: 30)
155151
```
156152

157-
### DPoP-Token Controller
153+
### How DPoP Works in Your Controllers
154+
155+
DPoP validation is handled entirely by the library at the filter level. Your controllers don't need any DPoP-specific code — the library validates the DPoP proof automatically before the request reaches your controller. A validated DPoP request produces the same `Auth0AuthenticationToken` as a Bearer request:
158156

159157
```java
160158
@RestController
161159
@RequestMapping("/api")
162-
public class DPoPController {
160+
public class SensitiveDataController {
163161
164162
@GetMapping("/sensitive")
165-
public ResponseEntity<Map<String, Object>> sensitiveEndpoint(
166-
Authentication authentication,
167-
HttpServletRequest request
168-
) {
169-
if (authentication instanceof Auth0AuthenticationToken) {
170-
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
171-
AuthenticationContext context = auth0Token.getAuthenticationContext();
172-
173-
Map<String, Object> response = new HashMap<>();
174-
response.put("user", authentication.getName());
175-
response.put("scheme", context.getScheme().toString());
176-
177-
if (context.getScheme() == AuthScheme.DPOP) {
178-
response.put("message", "Access granted with DPoP proof");
179-
response.put("dpop_bound", true);
180-
} else {
181-
response.put("message", "Access granted with Bearer token");
182-
response.put("dpop_bound", false);
183-
}
184-
185-
return ResponseEntity.ok(response);
163+
public ResponseEntity<Map<String, Object>> sensitiveEndpoint(Authentication authentication) {
164+
// This works the same whether the client used Bearer or DPoP.
165+
// DPoP proof validation already happened in the filter.
166+
if (authentication instanceof Auth0AuthenticationToken auth0Token) {
167+
return ResponseEntity.ok(Map.of(
168+
"user", authentication.getName(),
169+
"scopes", auth0Token.getScopes(),
170+
"message", "Access granted"
171+
));
186172
}
187173
188174
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
189175
}
190176
}
191177
```
192178

179+
The difference is in what the library **rejects**:
180+
- `ALLOWED` mode: Accepts both `Authorization: Bearer <token>` and `Authorization: DPoP <token>` + `DPoP: <proof>`
181+
- `REQUIRED` mode: Rejects Bearer tokens — only `DPoP` tokens with a valid proof are accepted
182+
- `DISABLED` mode: Rejects DPoP tokens — only `Bearer` tokens are accepted
183+
193184
## Scope-Based Authorization
194185

195-
### Method-Level Security
186+
The library maps JWT scopes to Spring Security authorities with a `SCOPE_` prefix. For example, a token with `scope: "read:messages write:messages"` produces authorities `SCOPE_read:messages` and `SCOPE_write:messages`.
187+
188+
### Option 1: Security Filter Chain (Recommended)
189+
190+
The simplest approach — define scope requirements in your security configuration:
191+
192+
```java
193+
@Configuration
194+
public class SecurityConfig {
195+
196+
@Bean
197+
SecurityFilterChain apiSecurity(
198+
HttpSecurity http,
199+
Auth0AuthenticationFilter authFilter
200+
) throws Exception {
201+
return http
202+
.csrf(csrf -> csrf.disable())
203+
.sessionManagement(session ->
204+
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
205+
)
206+
.authorizeHttpRequests(auth -> auth
207+
.requestMatchers("/api/public").permitAll()
208+
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
209+
.requestMatchers("/api/users/**").hasAuthority("SCOPE_read:users")
210+
.anyRequest().authenticated()
211+
)
212+
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
213+
.build();
214+
}
215+
}
216+
```
217+
218+
### Option 2: Method-Level Security with @PreAuthorize
219+
220+
For fine-grained control per method. Requires `@EnableMethodSecurity` on a configuration class:
221+
222+
```java
223+
@Configuration
224+
@EnableMethodSecurity
225+
public class MethodSecurityConfig {
226+
// Enables @PreAuthorize annotations
227+
}
228+
```
196229

197230
```java
198231
@RestController
199-
@RequestMapping("/api")
200-
@PreAuthorize("hasAuthority('SCOPE_read:users')")
232+
@RequestMapping("/api/users")
201233
public class UserManagementController {
202234
203-
@GetMapping("/users")
235+
@GetMapping
204236
@PreAuthorize("hasAuthority('SCOPE_read:users')")
205237
public ResponseEntity<List<User>> getUsers() {
206-
// Only accessible with 'read:users' scope
207238
return ResponseEntity.ok(userService.getAllUsers());
208239
}
209240
210-
@PostMapping("/users")
241+
@PostMapping
211242
@PreAuthorize("hasAuthority('SCOPE_write:users')")
212243
public ResponseEntity<User> createUser(@RequestBody User user) {
213-
// Only accessible with 'write:users' scope
214244
return ResponseEntity.ok(userService.createUser(user));
215245
}
216246
217-
@DeleteMapping("/users/{id}")
247+
@DeleteMapping("/{id}")
218248
@PreAuthorize("hasAuthority('SCOPE_delete:users')")
219249
public ResponseEntity<Void> deleteUser(@PathVariable String id) {
220-
// Only accessible with 'delete:users' scope
221250
userService.deleteUser(id);
222251
return ResponseEntity.noContent().build();
223252
}
224253
}
225254
```
226255

227-
### Custom Scope Validation
228-
229-
```java
230-
@Component
231-
public class ScopeValidator {
232-
233-
public boolean hasRequiredScopes(Authentication authentication, String... requiredScopes) {
234-
if (!(authentication instanceof Auth0AuthenticationToken)) {
235-
return false;
236-
}
237-
238-
Auth0AuthenticationToken auth0Token = (Auth0AuthenticationToken) authentication;
239-
DecodedJWT jwt = auth0Token.getJwt();
240-
String scopeClaim = jwt.getClaim("scope").asString();
241-
242-
if (scopeClaim == null) {
243-
return false;
244-
}
256+
### Option 3: Programmatic Scope Check
245257

246-
Set<String> tokenScopes = Set.of(scopeClaim.split(" "));
247-
return tokenScopes.containsAll(Arrays.asList(requiredScopes));
248-
}
249-
}
258+
Use `getScopes()` on the token directly when you need custom logic:
250259

260+
```java
251261
@RestController
252262
@RequestMapping("/api")
253-
public class CustomScopeController {
254-
255-
@Autowired
256-
private ScopeValidator scopeValidator;
263+
public class AdminController {
257264
258265
@GetMapping("/admin")
259266
public ResponseEntity<Map<String, Object>> adminEndpoint(Authentication authentication) {
260-
if (!scopeValidator.hasRequiredScopes(authentication, "admin", "read:admin")) {
261-
return ResponseEntity.status(HttpStatus.FORBIDDEN)
262-
.body(Map.of("error", "insufficient_scope"));
267+
if (authentication instanceof Auth0AuthenticationToken auth0Token) {
268+
Set<String> scopes = auth0Token.getScopes();
269+
270+
if (!scopes.contains("admin") || !scopes.contains("read:admin")) {
271+
return ResponseEntity.status(HttpStatus.FORBIDDEN)
272+
.body(Map.of("error", "insufficient_scope"));
273+
}
274+
275+
return ResponseEntity.ok(Map.of("message", "Admin access granted"));
263276
}
264277
265-
return ResponseEntity.ok(Map.of("message", "Admin access granted"));
278+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
266279
}
267280
}
268281
```
@@ -281,15 +294,15 @@ auth0:
281294
282295
# Optional: DPoP mode (DISABLED, ALLOWED, REQUIRED)
283296
# Default: ALLOWED
284-
dpopMode: ALLOWED
297+
dpop-mode: ALLOWED
285298
286299
# Optional: DPoP proof time window in seconds
287300
# Default: 300 (5 minutes)
288-
dpopIatOffsetSeconds: 300
301+
dpop-iat-offset-seconds: 300
289302
290303
# Optional: DPoP proof time leeway in seconds
291304
# Default: 30 (30 seconds)
292-
dpopIatLeewaySeconds: 30
305+
dpop-iat-leeway-seconds: 30
293306
```
294307

295308
### Environment Variables
@@ -299,22 +312,23 @@ You can also configure using environment variables:
299312
```bash
300313
AUTH0_DOMAIN=your-tenant.auth0.com
301314
AUTH0_AUDIENCE=https://api.example.com
302-
AUTH0_DPOP_MODE=ALLOWED
303-
AUTH0_DPOP_IAT_OFFSET_SECONDS=300
304-
AUTH0_DPOP_IAT_LEEWAY_SECONDS=30
315+
AUTH0_DPOPMODE=ALLOWED
316+
AUTH0_DPOPIATOFFSETSECONDS=300
317+
AUTH0_DPOPIATLEEWAYSSECONDS=30
305318
```
306319

320+
> **Note:** Spring Boot environment variable binding removes dashes and is case-insensitive. Do not use underscores to separate words within a property name (e.g., use `AUTH0_DPOPMODE`, not `AUTH0_DPOP_MODE`).
321+
307322
## Error Handling
308323

309324
### Common HTTP Status Codes
310325

311326
- **401 Unauthorized**: Missing or invalid token
312327
- **403 Forbidden**: Valid token but insufficient permissions
313-
- **400 Bad Request**: Invalid DPoP proof or malformed request
314328

315329
### WWW-Authenticate Headers
316330

317-
The library automatically sets appropriate `WWW-Authenticate` headers:
331+
The library automatically sets appropriate `WWW-Authenticate` headers on authentication failures:
318332

319333
```
320334
# ALLOWED mode (default)
@@ -326,12 +340,3 @@ WWW-Authenticate: DPoP algs="ES256"
326340
# DPoP-specific errors
327341
WWW-Authenticate: DPoP error="invalid_dpop_proof", error_description="DPoP proof validation failed"
328342
```
329-
330-
## Best Practices
331-
332-
1. **Environment-Specific Configuration**: Use different Auth0 domains and audiences for different environments
333-
2. **Scope Validation**: Always validate scopes for sensitive operations
334-
3. **Error Handling**: Implement comprehensive error handling for auth failures
335-
4. **Testing**: Use mocked authentication for unit tests and real tokens for integration tests
336-
5. **Security Headers**: Ensure proper CORS and security headers are configured
337-
6. **DPoP Mode**: Use `REQUIRED` mode for high-security APIs, `ALLOWED` for gradual adoption

0 commit comments

Comments
 (0)