Skip to main content

Protect your API on Spring Boot


This tutorial assumes you have created an API Resource http://localhost:3000/ in Admin Console. If you are not ready, read this before continuing.

Your web application may run on the server-side using Spring Boot framework. For now, you need to integrate Logto in Spring Boot manually. This article guides you on how to finish it step by step. And we use Gradle, Java, and Spring Security to take the example.

Start a Spring Boot projectโ€‹

With Spring Initializr, you can quickly start a Spring Boot project. Use the following options:

  • Gradle Project
  • Language: Java
  • Spring Boot: 2.7.2

Generate and open the project.

Add dependenciesโ€‹

Add the dependencies to your Gradle project build file build.gradle:

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'

Since Spring Boot and Spring Security have built-in support for both OAuth2 resource server and JWT validation, you DO NOT need to add additional libraries from Logto to integrate.

See Spring Security OAuth 2.0 Resource Server and Spring Security Architecture for more details.

Get issuer and JWKS URIโ€‹

All tokens are issued by the issuer, and signed with JWK (See JWS for more details).

Before moving on, you will need to get an issuer and a JWKS URI to verify the issuer and the signature of the Bearer Token (access_token).

By default, your Logto's issuer and JWKS URI are https://<your-logto-domain>/oidc and https://<your-logto-domain>/oidc/jwks (e.g. http://localhost:3001/oidc and http://localhost:3001/oidc/jwks in the local development environment).


All the latest Logto Authorization Server Configurations can be found by https://<your-logto-domain>/oidc/.well-known/openid-configuration (e.g. http://localhost:3001/oidc/.well-known/openid-configuration in the local development environment), including the issuer, jwks_uri and other authorization configs. For example:

// ...
"issuer": "https://<your-logto-domain>/oidc",
"jwks_uri": "https://<your-logto-domain>/oidc/jwks"
// ...

Configure applicationโ€‹

Use an application.yml file (instead of the default to configure the server port, audience, and OAuth2 resource server.

# path/to/project/src/main/resources/application.yaml
port: 3000

audience: http://localhost:3000/

issuer-uri: <your-logto-issuer-uri> # e.g. http://localhost:3001/oidc
jwk-set-uri: <your-logto-jwks-uri> # e.g. http://localhost:3001/oidc/jwks
  • audience: The unique API identifier (i.e. API indicator) of your protected API resource.
  • The iss claim value and the issuer URI in the JWT issued by Logto. Fill out the issuer value from the previous section.
  • Spring Security uses this URI to get the authorization server's public keys to validate JWT signatures. Fill out the jwks_uri value from the previous section.

Provide audience validatorโ€‹

Provide your own AudienceValidator class that implements the OAuth2TokenValidator interface to validate whether the required audience is present in the JWT.

// path/to/project/src/main/java/io/logto/springboot/sample/validator/
package io.logto.springboot.sample.validator;


public class AudienceValidator implements OAuth2TokenValidator<Jwt> {

private final String audience;

public AudienceValidator(String audience) {
this.audience = audience;

public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (!jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Required audience not found", null));

// Optional: For RBAC validate the scopes of the JWT.
String scopes = jwt.getClaimAsString("scope");
if (scopes == null || !scopes.contains("read:profile")) {
return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Insufficient permission", null));

return OAuth2TokenValidatorResult.success();

Configure Spring Securityโ€‹

Spring Security makes it easy to configure your application as a Resource Server and validate the JWT from the Bearer Token in the request header.

You need to provide instances of JwtDecoder and SecurityFilterChain (as Spring beans), and add the @EnableWebSecurity annotation.


Logto uses the ES384 algorithm to sign the JWTs by default. To decode the JWTs, you need to set the jwsAlgorithm to ES384 explicitly. If you prefer to use RSA, feel free to rotate the signing algorithm in the Logto Admin Console. Please refer to Signing keys for more details.

// path/to/project/src/main/java/io/logto/springboot/sample/configuration/
package io.logto.springboot.sample.configuration;

import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.SecurityContext;
import io.logto.springboot.sample.validator.AudienceValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

public class SecurityConfiguration {

private String audience;

private String issuer;

private String jwksUri;

public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwksUri)
// Logto uses the ES384 algorithm to sign the JWTs by default.
// The decoder should support the token type: Access Token + JWT.
.jwtProcessorCustomizer(customizer -> customizer.setJWSTypeVerifier(
new DefaultJOSEObjectTypeVerifier<SecurityContext>(new JOSEObjectType("at+jwt"))))

jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
new AudienceValidator(audience),
new JwtIssuerValidator(issuer),
new JwtTimestampValidator()));

return jwtDecoder;

public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
.oauth2ResourceServer(oauth2 -> oauth2
.authorizeHttpRequests(requests -> requests
// Allow all requests to the public APIs.
// Require jwt token validation for the protected APIs.


Add APIsโ€‹

Add a controller to provide the protected and public APIs:

// path/to/project/src/main/java/io/logto/springboot/sample/controller/
package io.logto.springboot.sample.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

// Only allow all origins for the sample.
// (Production applications should configure CORS carefully.)
@CrossOrigin(origins = "*")
public class ProtectedController {
public String protectedProfile() {
return "Protected profile.";

public String publicConfig() {
return "Public config.";

Access protected APIโ€‹

Build and run your Spring Boot web application, e.g. execute the bootRun Gradle task.

./gradlew bootRun

This tutorial assumes you have the Access Token for an API resource http://localhost:3000/ before making a request. If you are not ready, read this before continuing.

Request your protected API with the Access Token as the Bearer token in the Authorization header, e.g. execute the curl command.

curl --include 'http://localhost:3000/api/profile' \
--header 'Authorization: Bearer <your-access-token>'

If succeeded, you will get a response with 200 status:

HTTP/1.1 200

Otherwise, you will get a response with 401 status like this:

HTTP/1.1 401
WWW-Authenticate: Bearer error="invalid_token", error_description="An error occurred while attempting to decode the Jwt: Signed JWT rejected: Invalid signature", error_uri=""

Further readingsโ€‹