---
title: "Add JWT Authentication to Your Spring Boot API"
description: "Secure your Spring Boot API using JWT authentication with JWKS."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/java/springboot/jwt-backend"
framework: "Spring Boot"
language: "Java"
authStrategy: "JWT with JWKS"
pageType: use-case
---

# Add JWT Authentication to Your Spring Boot API

Secure your Spring Boot API using JWT authentication with JWKS.

## How Zuplo Handles It

Let Zuplo issue short-lived JWTs signed with a JWKS your Spring Boot backend can verify — no long-lived API keys touch your origin.

## Spring Boot Backend Code

```java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.client.RestTemplate;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Key;
import java.util.List;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import io.jsonwebtoken.security.Keys;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.ResponseEntity;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String ISSUER = "https://my-api-a32f34.zuplo.api/__zuplo/issuer";
    private static final String JWKS_URL = ISSUER + "/.well-known/jwks.json";

    @Autowired
    private RestTemplate restTemplate;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .addFilterBefore(new JwtAuthenticationFilter(), BasicAuthenticationFilter.class)
            .authorizeRequests().anyRequest().authenticated();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            String header = request.getHeader("Authorization");

            if (header == null || !header.startsWith("Bearer ")) {
                chain.doFilter(request, response);
                return;
            }

            String token = header.substring(7);

            try {
                Claims claims = Jwts.parserBuilder()
                        .setSigningKeyResolver(new SigningKeyResolverAdapter() {
                            @Override
                            public Key resolveSigningKey(io.jsonwebtoken.JwsHeader jwsHeader, Claims claims) {
                                String kid = jwsHeader.getKeyId();
                                return getSigningKey(kid);
                            }
                        })
                        .requireIssuer(ISSUER)
                        .build()
                        .parseClaimsJws(token)
                        .getBody();

                // Authentication
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(claims, null, List.of());
                SecurityContextHolder.getContext().setAuthentication(authentication);

            } catch (Exception e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
                return;
            }

            chain.doFilter(request, response);
        }

        private Key getSigningKey(String kid) {
            ResponseEntity<JsonNode> jwksResponse = restTemplate.getForEntity(JWKS_URL, JsonNode.class);
            JsonNode keys = jwksResponse.getBody().get("keys");

            for (JsonNode key : keys) {
                if (key.get("kid").asText().equals(kid)) {
                    byte[] publicKeyBytes = java.util.Base64.getDecoder().decode(key.get("x5c").get(0).asText());
                    return Keys.hmacShaKeyFor(publicKeyBytes);
                }
            }

            throw new IllegalArgumentException("No matching key found");
        }
    }
}
```

## Example Request

```bash
curl -X GET \
  'https://your-api.zuplo.dev/your-route' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

## Learn More

- [API Key Authentication on Zuplo](https://zuplo.com/docs/policies/api-key-auth-inbound)
- [JWT Authentication on Zuplo](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound)
- [All use cases](https://zuplo.com/use-cases)
