DEV Community

Cover image for Keycloak + Spring: A Practical Guide to Securing Your APIs
Ronyeri Marinho
Ronyeri Marinho

Posted on

Keycloak + Spring: A Practical Guide to Securing Your APIs

In modern applications, sooner or later you will face authentication and authorization: login, access control, tokens, roles, refresh tokens, password security… the list is long. Implementing all of this from scratch in every project is not only time-consuming, but also significantly increases the risk of security flaws and inconsistencies between systems. And this is exactly where Keycloak comes in.

Keycloak is an Identity and Access Management (IAM) server that centralizes the entire authentication and authorization layer of your application, solving many common problems such as:

  • User management
  • Login and logout
  • Permissions and roles management
  • Token issuance and validation (OAuth 2.0 / OpenID Connect)
  • Integration with multiple applications using the same identity base
  • Easy integration with social login providers (Google, GitHub, etc.)

But why integrate it with Spring? The answer lies in the combination of productivity and security. By integrating Keycloak with your API, you drastically reduce the amount of manual configuration required for these flows and delegate the responsibility to a specialized tool, consuming only already validated JWT tokens in your application.

In addition, centralizing the identity provider fits perfectly into microservices architectures, allowing multiple applications to share the same authentication system and enabling a true single sign-on experience for users.

If you have ever struggled to build all this “by hand”, this guide will save you a significant amount of time.


1. A brief tour of Keycloak’s main features
The goal here is to stay as hands-on as possible, but some concepts need to be introduced first so that the configuration makes sense and does not turn into a purely mechanical sequence of steps without context.

Keycloak is an open-source Identity and Access Management (IAM) platform that centralizes authentication (proving who the user is) and authorization (defining what they are allowed to do). Instead of each application implementing these responsibilities individually, they delegate this work to an identity server.

Compatible with widely adopted standards such as OAuth 2.0, OpenID Connect, and SAML 2.0, Keycloak integrates easily with web applications, mobile apps, and REST APIs, regardless of the backend technology.

In practice, it provides a built-in, ready-to-use, and fully customizable login page, along with several endpoints for authentication, token refresh, and logout. This allows applications to use either a standard login interface or purely API-based authentication flows, without being tied to a specific UI model.

In addition, Keycloak offers a complete administrative interface for managing users, roles, and permissions, as well as integration with external identity providers such as Google, GitHub, and others.

It is worth noting that Keycloak is a very robust and feature-rich tool. In this guide, we will focus on the essentials to get you started, but for a deeper understanding, the official documentation and complementary materials are excellent next steps.


2. Running Keycloak and PostgreSQL with Docker
In this step, we will run Keycloak inside Docker containers and use PostgreSQL as the database.

To simplify the setup, we will use a docker-compose.yml file containing only the essentials: the Keycloak service, the PostgreSQL service, and the configuration required for both to communicate properly.

Below is the content of the file:

services:
  db:
    container_name: psql-db-demo
    image: postgres
    restart: always
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=root
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init-keycloak-database.sql:/docker-entrypoint-initdb.d/init-keycloak-db.sql
    ports:
      - 5432:5432
  keycloak:
    container_name: keycloak-demo
    image: quay.io/keycloak/keycloak:26.1.0
    environment:
      - KC_BOOTSTRAP_ADMIN_USERNAME=admin
      - KC_BOOTSTRAP_ADMIN_PASSWORD=admin
      - KC_DB=postgres
      - KC_DB_URL=jdbc:postgresql://db:5432/keycloak
      - KC_DB_USERNAME=postgres
      - KC_DB_PASSWORD=root
    ports:
      - 8181:8080
    command: start-dev
    depends_on:
      - db
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "root"]
      interval: 10s
      retries: 5
volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

This file defines:

  • A PostgreSQL container, with user, password, exposed port, and a volume for data persistence;
  • A Keycloak container, with a property configured to run in development mode (start-dev);
  • The necessary environment variables for Keycloak to automatically connect to the database.

In addition, we include a database initialization script named init-keycloak-database.sql, located at the root of the project, with the following content:

CREATE DATABASE keycloak WITH ENCODING 'UTF8';
Enter fullscreen mode Exit fullscreen mode

This script ensures that the database used by Keycloak is automatically created when the PostgreSQL container starts for the first time.

Pay special attention to the following variable:

KC_DB_URL=jdbc:postgresql://db:5432/keycloak
Enter fullscreen mode Exit fullscreen mode

Here, db is the name of the PostgreSQL service defined in docker-compose.yml, allowing the containers to find each other through Docker’s internal network without the need for manual IP configuration. With everything configured, simply run:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

In just a few seconds, your Keycloak and PostgreSQL instances will be up and running. I used simple usernames, passwords, and ports to make the example easier to follow, but feel free to customize them according to your needs.

The Keycloak admin console will be available at:

http://localhost:8181
Enter fullscreen mode Exit fullscreen mode

Figure 1 — Keycloak Admin Console login screen. Figure 1 — Keycloak Admin Console login screen.

To access it, use the username and password defined in the environment variables:

  • KC_BOOTSTRAP_ADMIN_USERNAME
  • KC_BOOTSTRAP_ADMIN_PASSWORD

3. What needs to be configured in Keycloak?
After logging into the admin console, you will quickly notice the number of features and configuration options available. Let’s start with the most important concept of all: the Realm.

3.1 Configuring the Realm
A realm in Keycloak works as an isolated space that contains all the security configuration for one or more applications. It is inside a realm that we define users, credentials, roles, permissions, and all the other elements we will see next.

Think of a realm as the security universe of your project. Each realm is completely independent from the others, which makes it perfect for separating different projects or even environments such as development, staging, and production.

At this point, you have probably noticed, in the top-left corner of the screen, the existence of a realm called master. This realm is responsible for managing the Keycloak instance itself — including the user we are currently using to access the admin console — and for this reason, it is not recommended to use it for applications.

So let’s create our own.

Click on Create realm, choose a name that makes sense for your context, and complete the creation. With that, we now have our isolated security environment ready to receive the next configurations.

Figure 2 — Keycloak home screen with the master realm.Figure 2 — Keycloak home screen with the master realm.

3.2 Configuring the Client
A client represents an application or service that will use Keycloak to authenticate users or validate access. In other words, the client is how your application exists inside Keycloak.

This is where we define:

  • how the application communicates with Keycloak,
  • which authentication flows are enabled,
  • and which security behaviors are allowed.

In the menu of your new realm, go to the Clients section and create a new one. Some default clients will already exist and can be ignored.

When creating it, fill in:

  • Client ID: the application identifier (in my case: keycloak-spring-xp)
  • Name e Description: optional, but useful for organization.

Figure 3 — Client general configuration form.Figure 3 — Client general configuration form.

3.2.1 Client Types
The client type defines how the application authenticates with Keycloak:

  • Public: used by applications that cannot securely store secrets, such as SPAs (e.g., Angular, React). This type does not require a client secret for authentication.
  • Confidential (private): used by applications running on secure servers, such as backends and APIs. These applications use a client secret for authentication, ensuring more secure communication with the identity server.

Since we are working with an API, we choose the Confidential type.

3.2.2 Authentication Flows
Keycloak offers several flows. The main ones are:

  • Standard Flow: the most recommended flow from a security standpoint. It is used in applications that have a frontend and support redirection. In this model, the user does not receive the access token directly, but rather an authorization code, which is later exchanged for tokens.
  • Implicit Flow (not recommended): similar to the Standard Flow, but returns the token directly after authentication. Because it exposes the token and introduces security risks, its use is discouraged.
  • Direct Access Grants: recommended only for specific scenarios, like ours, where there is only an API and no frontend. This flow allows direct authentication via API using username and password.
  • Service Accounts Roles: used for system-to-system authentication, allowing a service to authenticate with another without the involvement of an end user.

Figure 4 — Client flow configuration form.Figure 4 — Client flow configuration form.

Redirect configuration (URLs) is important for applications with a frontend, as it defines where Keycloak should redirect the user after events such as login, logout, and others. In our scenario, this configuration does not directly affect our authentication flow.

Figure 5 — Client login configuration form.Figure 5 — Client login configuration form.

3.3 Access Token Lifetime
At this point, we have created the isolated security space (Realm) and the resource that represents our application and defines how it authenticates with Keycloak (Client). With this, we already have the essentials needed to integrate the Spring application and protect it with Keycloak.

By default, the access token lifetime is 5 minutes. This value can be adjusted in the Realm settings by navigating to:

Realm Settings → Tokens → Access Token Lifespan.

Figure 6 — Token-related configuration screen.Figure 6 — Token-related configuration screen.

These are the minimum configurations required to start protecting your API. Keycloak offers many more features, and my recommendation is to explore the official documentation and additional guides at your own pace.


4. How does the API connect to Keycloak?
The first step was to create the API using Spring Initializr and add the required dependencies. For our context, the most important ones are:

  • Spring Security: responsible for intercepting HTTP requests, protecting the application’s endpoints, and enforcing authentication and authorization rules.
  • OAuth2 Resource Server: pallows the API to act as a Resource Server, trusting Keycloak as the Authorization Server and only validating the JWT tokens already issued by it, without implementing any custom authentication logic.

Figure 7 — Project configuration screen in Spring Initializr.Figure 7 — Project configuration screen in Spring Initializr.

To keep the example focused and simple, a basic Hello World endpoint was created:

// imports omitted...

@RestController
public class HelloWorldController {

    @GetMapping("hello-world")
    public String saysHelloWorld() {
        return "Hello World!";
    }
}
Enter fullscreen mode Exit fullscreen mode

At this point, the response of this endpoint will be 401 (Unauthorized) if you try to access it. This behavior is expected and is part of the secure by default principle (deny by default): once Spring Security is added, all requests are automatically protected and require a valid token.

4.1 Configuring application.yml
For the API to accept tokens issued by Keycloak, we need to inform who the token issuer is and how to validate its signature.

The issuer-uri property represents the entity that issues the tokens — in our case, the Keycloak Realm. With this information, Spring uses the OpenID Connect Discovery mechanism to automatically discover the endpoints and keys required for validation.

The jwk-set-uri property points directly to the endpoint that exposes the public keys used to verify the signature of JWT tokens.

These configurations are done in the application.yml (or application.properties, depending on your project setup):

spring:
  application:
    name: keycloak-spring-xp
  security:
    oauth2:
      resource-server:
        jwt:
          issuer-uri: ${APP_OAUTH2_JWK_ISSUER_URI:http://localhost:8181/realms/keycloak-demo}
          jwk-set-uri: ${spring.security.oauth2.resource-server.jwt.issuer-uri}/protocol/openid-connect/certs
Enter fullscreen mode Exit fullscreen mode

Now we need to configure application security to behave as a stateless Resource Server, requiring authentication for all requests and automatically validating JWT tokens:

// imports omitted...

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .cors(Customizer.withDefaults())
                .csrf(CsrfConfigurer::disable)
                .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
                        authorizationManagerRequestMatcherRegistry
                                .anyRequest().authenticated()
                )
                .oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer ->
                        httpSecurityOAuth2ResourceServerConfigurer.jwt(Customizer.withDefaults()))
                .sessionManagement(httpSecuritySessionManagementConfigurer ->
                        httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .build();
    }
}
Enter fullscreen mode Exit fullscreen mode

And that’s it.

At this point, Spring already trusts Keycloak as the identity provider, and every request must present a valid token to access any API endpoint. But a few questions remain:

How do we authenticate?
With which user?
And what is the authentication endpoint?

That is exactly what we will cover in the next chapter.


5. Obtaining and using the access token
Inside the Keycloak instance, go to Realm Settings and, in the General tab, scroll down to the Endpoints section. There you will find two important links — the first one is the OpenID Endpoint Configuration.

When you click this link, Keycloak will display a document containing all the URLs related to the OpenID Connect protocol. Among them is the endpoint responsible for issuing tokens. Example:

http://localhost:8181/realms/keycloak-demo/protocol/openid-connect/token
Enter fullscreen mode Exit fullscreen mode

It is through this endpoint that we authenticate with Keycloak and obtain the access token, which will then be sent in requests to our Spring API.

In our scenario, we use this endpoint to authenticate via Direct Access Grants. The complete structure of the request in Postman can be seen below:

Figure 8 — HTTP request for authentication in Keycloak using Postman.Figure 8 — HTTP request for authentication in Keycloak using Postman.

5.1 Authentication request parameters
To authenticate against Keycloak’s token endpoint, we need to send a few parameters in the request body. Each one plays a specific role in the process:

  • Grant Type: defines which authentication flow is being used. In our case, we use password, which indicates the Direct Access Grants flow, where the user authenticates directly with username and password. This parameter is essential for Keycloak to understand how to process the request.
  • Client ID: identifies which Client is requesting the token. It represents the application (our API, for example) previously registered in Keycloak and determines which flows, permissions, and security configurations will be applied.
  • Username: the username registered in Keycloak that is attempting to authenticate.
  • Password: the password of the specified user.
  • Client Secret: used only when the client is of type confidential. It acts as the application’s own credential, ensuring that only authorized clients can request tokens.

Together, these parameters allow Keycloak to validate:

  • who the user is,
  • which application is requesting access,
  • and which authentication flow is being used,

and then return an access token that can be used to access the protected API endpoints.

5.2 Creating a user in Keycloak
If we were using Keycloak’s default frontend, we could enable the registration screen and allow users to sign up themselves. Since we are only testing the API, we will create a user manually.

In the Users menu within the Realm, create a new user by filling in the fields as shown below. Although only the username is mandatory, Keycloak will require the remaining basic information during authentication.

Figure 9 — User creation form in Keycloak.Figure 9 — User creation form in Keycloak.

The password is defined in the Credentials tab of the created user. After that, simply fill in the request in Postman with the user and client data, and the access token will be generated:

Figure 10 — Authentication response from Keycloak.Figure 10 — Authentication response from Keycloak.

Finally, copy the returned access token and send it in the Authorization header of the request to the API:

Authorization: Bearer <access_token>
Enter fullscreen mode Exit fullscreen mode

Access will now be granted, and the endpoint will respond correctly:

Figure 11 — API request with a validated access token.Figure 11 — API request with a validated access token.

This chapter now completes the full cycle:

Keycloak → token → protected API → validated access.


Final considerations
The journey was long, but if you made it this far, there is a very good chance that everything is already properly configured and working.

Of course, this is not a complete guide to Keycloak. In fact, it represents only the beginning of a whole universe of possibilities that the tool offers. The goal here was to build a solid foundation so that you can move forward with more confidence and autonomy from this point on.

I sincerely hope this content has been useful in some way in your daily work as a developer. If any questions arise, if you have suggestions, or if you simply want to exchange ideas about the topic, feel free to connect with me on LinkedIn. I also leave the link to the GitHub repository, where the example project used in this article is available.

Thank you for following along until the end — and see you next time!

Top comments (0)