---
title: "Secure Your Spring Boot API with Auth0 in Minutes"
description: "Secure your Spring Boot API in minutes using the new Auth0 Spring Boot starter. Includes built-in DPoP support to stop stolen-token replay attacks."
authors:
  - name: "Tanya Sinha"
    url: "https://auth0.com/blog/authors/tanya-sinha/"
date: "May 6, 2026"
category: "Announcements"
tags: ["java", "dpop", "jwt"]
url: "https://auth0.com/blog/secure-spring-boot-api-with-auth0/"
---

# Secure Your Spring Boot API with Auth0 in Minutes

<style>
    
  /* Increases spacing between bullet points */   
    li {padding-bottom: .7em; }
/* Style a table. Add borders, center table, and reduce font size. */
  table {
    width: 90%;
    margin: 2.4rem auto !important;
    border-collapse: collapse;
    font-size: .9em;
  }
  table, td, th {
    border: 1px solid;
  }
  table th {
    line-height: normal;
    padding: .8em;
  }
  td {
    padding: .8em;
    line-height: normal;
  }
</style>
There is a rite of passage every Spring Boot developer knows too well. You need to protect an API endpoint. "How hard can it be?" you think. Three hours later, you are deep in a StackOverflow thread about JSON Web Key Set (JWKS) rotation, wondering why your custom `BearerTokenResolver` is silently swallowing errors.

JWT validation should not be a side quest.

We are releasing `auth0-springboot-api`, a library that gives you production-grade Auth0 security in two lines of config. It handles JWT validation, JWKS caching, scope mapping, error responses, and, here is the interesting part, built-in [Demonstration of Proof-of-Possession (DPoP) support](https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop) support to stop stolen-token replay attacks.

Let's see what "minimal configuration" for securing a Spring Boot API with JWTs actually looks like with Auth0.

## What You Are Not Writing Anymore

Before we look at the solution, let's be honest about what securing a Spring Boot API with JWTs actually involves. Even with Spring Security's resource server support, you are still on the hook for:

1. Fetching and caching [JWKS](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets) public keys from your Auth0 domain  
2. Validating JWT signatures (RSA256)  
3. Verifying `iss` and `aud` claims against your tenant  
4. Mapping token scopes to Spring Security authorities  
5. Returning standards-compliant `WWW-Authenticate` headers on failure

That is five concerns before you write a single line of business logic. And if you need [DPoP (RFC 9449\)](https://datatracker.ietf.org/doc/html/rfc9449)? Add DPoP proof parsing, ES256 signature verification, JWK thumbprint binding, and time-based nonce validation to the list.

All of this is now handled for you. Here is how.

## Four Steps to a Secured Spring Boot API

Let's walk through the four steps to secure your Spring Boot API with Auth0.

### 1. Add the dependency

Add the library to your project using Maven or Gradle. This single dependency brings in everything you need: JWT validation, JWKS key management, scope mapping, and DPoP support.

```bash
<!-- Maven -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>auth0-springboot-api</artifactId>
    <version>1.0.0-beta.0</version>
</dependency>
```

```bash
// Gradle
implementation 'com.auth0:auth0-springboot-api:1.0.0-beta.0'
```

### 2. Point it at your Auth0 tenant

Two properties. That is all the library needs, it auto-discovers JWKS endpoints, issuer URLs, and signing algorithms from your domain:

```bash
auth0:
  domain: "your-tenant.auth0.com"
  audience: "https://your-api-identifier"
```

### 3. Wire up the security filter

The library auto-configures an `Auth0AuthenticationFilter` bean. You just drop it into your filter chain:

```bash
@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain apiSecurity(HttpSecurity http, Auth0AuthenticationFilter authFilter) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s ->
                s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/protected").authenticated()
                .anyRequest().permitAll())
            .addFilterBefore(authFilter,
                UsernamePasswordAuthenticationFilter.class)
            .build();
    }
}
```

No decoder beans, converter classes, or JWKS configuration. The filter is auto-configured and injected by Spring Boot.

### 4. Write your endpoints

With the security configuration in place, let's define a sample public and a protected endpoint to see it all in action.

```bash
@RestController
public class ApiController {

    @GetMapping("/api/public")
    public String publicEndpoint() {
        return "Anyone can see this.";
    }

    @GetMapping("/api/protected")
    public String protectedEndpoint(Authentication authentication) {
        return "Hello, " + authentication.getName() + "!";
    }
}
```

That is a complete, secured API. The library validates signatures, checks claims, maps scopes, and returns proper error responses, all behind that one filter.

## Now the Interesting Part: DPoP

Here is a question worth pausing on: **What happens if someone intercepts one of your Bearer tokens?**

The answer is uncomfortable. They can replay it. From any device, any network, any location. A Bearer token is a key whoever holds it walks in.

[DPoP](https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop) changes the equation. It cryptographically binds the token to the client that requested it. Think of the difference this way:

* **Bearer token** = a house key. Whoever has it can walk in.  
* **DPoP token** = a house key + fingerprint scanner. Having the key alone is not enough.

Enabling it takes one line:

```bash
auth0:
  domain: "your-tenant.auth0.com"
  audience: "https://your-api-identifier"
  dpopMode: ALLOWED                      # ← that is it
```

Your API now accepts both Bearer and DPoP-bound tokens. No changes to controllers, no new annotations. The library validates DPoP proofs at the filter level before requests ever reach your code.

### Three enforcement levels

The library supports three DPoP modes, so you can start simple and tighten security as your needs evolve

| Mode | Behavior | When to use |
| :---- | :---- | :---- |
| **DISABLED** | Bearer tokens only | Standard APIs without DPoP requirements |
| **ALLOWED** | Accepts both Bearer and DPoP | Rolling out DPoP gradually: existing clients keep working |
| **REQUIRED** | DPoP only, rejects Bearer | High-security APIs: financial services, healthcare, compliance-driven |

The recommended path: start with **ALLOWED**, migrate clients, then flip to **REQUIRED**. Zero downtime, zero controller changes.

For details on generating DPoP proofs on the client side, see the [Auth0 DPoP documentation](https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop).

## Working with Claims and Scopes

Token validation tells you who is calling. Scopes tell you *what they are allowed to do*.

### Reading JWT claims

Cast to `Auth0AuthenticationToken` and pull any claim directly, no manual decoding required:

```bash
@GetMapping("/api/profile")
public Map<String, Object> profile(Authentication authentication) {
    Auth0AuthenticationToken token =
        (Auth0AuthenticationToken) authentication;

    return Map.of(
        "subject", token.getClaim("sub"),                                     
        "scopes", token.getScopes()
    );
}
```

### Enforcing scopes

The library maps JWT scopes to Spring Security authorities with a `SCOPE_ prefix`. A token with `"read:users write:users"` produces `SCOPE_read:users` and `SCOPE_write:users`.

You can enforce scopes at the **route level** in your security filter chain:

```bash
.authorizeHttpRequests(auth -> auth
    .requestMatchers("/api/admin/**")
        .hasAuthority("SCOPE_admin")
    .requestMatchers("/api/users/**")
        .hasAuthority("SCOPE_read:users")
    .anyRequest().authenticated())
```

Or you can also enforce scopes at the **method level** using @PreAuthorize: 

```bash
@GetMapping("/api/users")
@PreAuthorize("hasAuthority('SCOPE_read:users')")
public List<User> getUsers() {
    return userService.getAllUsers();
}
```

Method-level security requires `@EnableMethodSecurity` on a configuration class. See the [Spring Security docs](https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html).

## What Happens Under the Hood

You do not need to know this to use the library. But if you are the kind of developer who opens the hood before driving, here is the request lifecycle in five steps:

1. **Extract:**  The filter pulls the `Authorization` header (and `DPoP` header, if present).  
2. **Select strategy:** Based on `dpopMode`, it picks Bearer-only, DPoP-only, or auto-detect.  
3. **Validate JWT:** Fetches JWKS keys, verifies RSA256 signature, checks `iss` and `aud`.  
4. **Validate DPoP proof:** *(if applicable)* Verifies ES256 signature, checks HTTP method/URL binding, confirms JWK thumbprint match.  
5. **Populate SecurityContext:** Creates an `Auth0AuthenticationToken` with the sub claim as principal and scopes as `SCOPE_*` authorities.

If anything fails, you get a `401` or `403` with a standards-compliant `WWW-Authenticate` header. Your controllers never see invalid tokens.

## Try It in 60 Seconds

You can try it yourself using the [`auth0-auth-java` repo](https://github.com/auth0/auth0-auth-java), which includes a ready-to-run playground app. If you already have an Auth0 tenant, you can be testing authenticated requests in under a minute.

```bash
# Clone and configure
git clone https://github.com/auth0/auth0-auth-java.git
cd auth0-auth-java

# Edit auth0-springboot-api-playground/src/main/resources/application.yml
# with your Auth0 domain and audience

# Run it
./gradlew :auth0-springboot-api-playground:bootRun
```

Then test:

```bash
# Public endpoint — no token needed
curl http://localhost:8080/api/public

# Protected endpoint — get a token first
curl -X POST https://YOUR_DOMAIN/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "audience": "https://my-springboot-api",
    "grant_type": "client_credentials"
  }'

# Use the token
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
     http://localhost:8080/api/protected
```

Curious what is inside the token? Paste the `access_token` value into [jwt.io](https://jwt.io/) to see the decoded claims.

The entire playground is three files: a Spring Boot entry point, a 12-line security config, and a controller with two endpoints. No JWT decoder beans. No JWKS configuration classes.

**New to Auth0?** The [Spring Boot API Quickstart](https://auth0.com/docs/quickstart/backend/java-spring-security5) walks you through creating an API in the Auth0 Dashboard, configuring a machine-to-machine app, and testing your endpoints step by step.

## What Is Next

This is a **beta release**, and we shipped it early intentionally. We want it shaped by real-world usage, not just internal testing.

If you hit a rough edge, have a feature idea, or just want to tell us what worked. We are listening:

* [File an issue on GitHub](https://github.com/auth0/auth0-auth-java/issues)  
* [Join the Auth0 Community](https://community.auth0.com/)

Security should not be the hardest part of your Spring Boot app. Go build something or [sign up and get started](https://auth0.com/signup).

## Resources

* [GitHub Repository](https://github.com/auth0/auth0-auth-java)  
* [Spring Boot API Quickstart](https://auth0.com/docs/quickstart/backend/java-spring-security5)  
* [Examples and Patterns](https://github.com/auth0/auth0-auth-java/blob/main/auth0-springboot-api/EXAMPLES.md)  
* [Auth0 DPoP Documentation](https://auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop)  
* [RFC 9449 — DPoP Specification](https://datatracker.ietf.org/doc/html/rfc9449)  
* [Sign up for a free Auth0 account](https://auth0.com/signup)

## Quick Reference

| Task | How |
| :---- | :---- |
| Add the dependency | `com.auth0:auth0-springboot-api:1.0.0-beta.0` |
| Configure Auth0 | `domain + audience` in `application.yml` |
| Enable DPoP | Add `dpopMode: ALLOWED` |
| Get user identity | `authentication.getName()` |
| Read any claim | `auth0Token.getClaim("email")` |
| Enforce scopes | `.hasAuthority("SCOPE\_read:users")` or `@PreAuthorize |`
| Run the playground | `./gradlew :auth0-springboot-api-playground:bootRun |`
