Building a Secure API with Quarkus, PostgreSQL, Kong, and OAuth2

by Didin J. on Jun 10, 2025 Building a Secure API with Quarkus, PostgreSQL, Kong, and OAuth2

Secure your Quarkus API with PostgreSQL, Keycloak OAuth2, and Kong Gateway using OIDC. Learn to build robust, production-ready microservices.

In today’s digital landscape, APIs are the backbone of modern applications, powering everything from mobile apps to complex microservices architectures. With that, increasing reliance comes an even greater need for robust security. Unauthorized access, data leaks, and misconfigured authentication can all lead to costly breaches. That’s why securing your API isn’t just an option—it’s a necessity.

In this tutorial, we’ll walk you through building a secure and scalable RESTful API using Quarkus, a modern Java framework designed for cloud-native development. We’ll connect it to a PostgreSQL database for data persistence, place it behind the Kong API Gateway for centralized traffic control and plugin-based enhancements, and secure it using OAuth2 for token-based authentication.

By the end of this guide, you’ll have a fully functional, secure API that:

  • Connects to a PostgreSQL database using JPA and Hibernate
  • Secures endpoints using OAuth2 access tokens (via Keycloak or any OAuth2 provider)
  • Exposes the API through Kong, enabling centralized control and token validation

This tutorial is perfect for developers who want a complete, real-world setup for building and securing APIs in production environments. Whether you're deploying a microservices architecture or building a backend for a mobile app, the principles and stack used here will set you up for success.


Project Setup

Before we dive into building the API, let’s set up the development environment and scaffold our Quarkus project.

Prerequisites

Make sure you have the following tools installed:

  • Java 17 or higher
  • Apache Maven 3.8+
  • Docker & Docker Compose
  • curl or Postman (for testing APIs)

Initialize the Quarkus Project

We'll use the Quarkus Maven plugin to scaffold the project.

Run the following command in your terminal:

mvn io.quarkus:quarkus-maven-plugin:3.23.2:create \
    -DprojectGroupId=com.djamware \
    -DprojectArtifactId=secure-api \
    -DclassName="com.djamware.resource.ProductResource" \
    -Dpath="/products" \
    -Dextensions="resteasy-reactive,jpa,hibernate-orm,quarkus-jdbc-postgresql,oidc,hibernate-orm-panache"

This command sets up a Quarkus project with:

  • RESTEasy Reactive: For building reactive REST APIs
  • JPA + Hibernate ORM: For database interaction
  • PostgreSQL: As our database
  • OIDC: To enable OAuth2 support

Navigate to the new project folder:

cd secure-api

Project Structure Overview

The generated project should have a structure like this:

Building a Secure API with Quarkus, PostgreSQL, Kong, and OAuth2 - quarkus project structure

We’ll be adding our entity, repository, and database configuration next.


Configuring PostgreSQL with Docker and Integrating it into Quarkus

To keep things clean and reproducible, we’ll run PostgreSQL using Docker and connect it to our Quarkus app using JDBC and Hibernate ORM.

Step 1: Create a docker-compose.yml

In the root of your project (secure-api/), create a file named docker-compose.yml with the following content:

version: '3.8'
services:
  postgres:
    image: postgres:15
    container_name: quarkus_postgres
    restart: always
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: securedb
      POSTGRES_USER: quarkus
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

✅ This sets up a PostgreSQL database named securedb with the user quarkus and password secret.

Start the container:

docker compose up -d

Step 2: Configure Quarkus to Connect to PostgreSQL

Open the src/main/resources/application.properties file and replace its contents with:

# Database Configuration
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=secret
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/securedb
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

# Enable OIDC (OAuth2) for later steps
quarkus.oidc.auth-server-url=http://localhost:8080/realms/secure-api
quarkus.oidc.client-id=secure-api-client
quarkus.oidc.credentials.secret=secret
quarkus.oidc.tls.verification=none
quarkus.oidc.application-type=service

🚨 We’re using drop-and-create for development only. For production, switch to validate or update.

Step 3: Create a Sample Entity and Repository

Let’s build a simple Product entity.

Product.java

Create a file at src/main/java/com/djamware/model/Product.java:

package com.djamware.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;

    @Column(nullable = false)
    public String name;

    public String description;
    public double price;
}

ProductRepository.java

Create src/main/java/com/djamware/repository/ProductRepository.java:

package com.djamware.repository;

import com.djamware.model.Product;

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class ProductRepository implements PanacheRepository<Product> {
}


Building the CRUD API with Quarkus RESTEasy Reactive

We'll now expose a RESTful API at /products to manage product data stored in PostgreSQL.

Folder Structure So Far

Your project should now look something like:

Building a Secure API with Quarkus, PostgreSQL, Kong, and OAuth2 - new project structure

Step 1: Create the REST Resource

In src/main/java/com/djamware/resource/ProductResource.java, define the endpoints:

package com.djamware.resource;

import java.util.List;

import com.djamware.model.Product;
import com.djamware.repository.ProductRepository;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ProductResource {

    @Inject
    ProductRepository repository;

    @GET
    public List<Product> getAll() {
        return repository.listAll();
    }

    @GET
    @Path("/{id}")
    public Product getById(@PathParam("id") Long id) {
        return repository.findById(id);
    }

    @POST
    @Transactional
    public Product create(Product product) {
        repository.persist(product);
        return product;
    }

    @PUT
    @Path("/{id}")
    @Transactional
    public Product update(@PathParam("id") Long id, Product updated) {
        Product product = repository.findById(id);
        if (product == null) {
            throw new NotFoundException();
        }
        product.name = updated.name;
        product.description = updated.description;
        product.price = updated.price;
        return product;
    }

    @DELETE
    @Path("/{id}")
    @Transactional
    public void delete(@PathParam("id") Long id) {
        repository.deleteById(id);
    }
}

✅ All methods are reactive but run in blocking mode since we’re using JPA/Hibernate with a JDBC datasource.

Test the Endpoints

Start the Quarkus app:

./mvnw quarkus:dev

Now test the endpoints using Postman or curl. Example:

curl -X POST http://localhost:8080/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Laptop","description":"Linux Dev Machine","price":999.99}'

You now have a fully working REST API!


Securing the API with OAuth2 and Keycloak

OAuth2 is the industry standard for securing APIs. Quarkus makes it simple to integrate using the quarkus-oidc extension. We’ll use Keycloak, an open-source identity and access management solution, as our OAuth2 provider.

Step 1: Run Keycloak with Docker

Add Keycloak to your existing docker-compose.yml:

  keycloak:
    image: quay.io/keycloak/keycloak:24.0
    container_name: quarkus_keycloak
    command: start-dev
    ports:
      - "8080:8080"
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin

Update and start the services:

docker compose up -d

Access Keycloak: http://localhost:8080

Building a Secure API with Quarkus, PostgreSQL, Kong, and OAuth2 - keycloak cms


Step 2: Configure Keycloak Realm and Client

1. Log into Keycloak Admin Console (admin/admin)

2. Create a new realm named secure-api by clicking the "Keycloak" dropdown

3. Create a new client (click the Client menu):

  •    Client ID: secure-api-client
  •    Client Protocol: openid-connect
  •    Access Type: confidential
  •    Root URL: http://localhost:8080
  •    Save, then set Valid Redirect URIs to * and copy the Client Secret (make sure the Client authentication is on)

4. Create a Role (e.g., user)

5. Create a User:

  •    Username: testuser
  •    Password: test123
  •    Assign the user role to this user

Step 3: Update Quarkus application.properties

Update your application.properties with the Keycloak client config:

# Enable OIDC (OAuth2) for later steps
quarkus.oidc.auth-server-url=http://localhost:8080/realms/secure-api
quarkus.oidc.client-id=secure-api-client
quarkus.oidc.credentials.secret=<YOUR_KEYCLOACK_CLIENT_SECRET>
quarkus.oidc.tls.verification=none
quarkus.oidc.application-type=service
# Require auth for all endpoints
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated

Step 4: Secure Endpoints with Roles

Update ProductResource.java to restrict access:

import jakarta.annotation.security.RolesAllowed;

@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed("user")
public class ProductResource {
  // ... same as before
}

Step 5: Obtain Access Token & Test

Before re-running the Quarkus app, change the port in application.properties:

quarkus.http.port=8081

Use curl or Postman to get a token:

curl -X POST http://localhost:8080/realms/secure-api/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "client_id=secure-api-client" \
  -d "client_secret=<COPY_FROM_KEYCLOAK>" \
  -d "username=testuser" \
  -d "password=test123"

Use the access_token in an API call:

curl -H "Authorization: Bearer <ACCESS_TOKEN>" http://localhost:8081/products

Unauthenticated or invalid tokens will result in 401 Unauthorized.


Setting Up Kong API Gateway to Secure and Route Traffic to Quarkus

In this section, we’ll deploy Kong Gateway in front of our Quarkus API. Kong will act as a reverse proxy, handling:

  • Routing
  • Token validation (via plugin)
  • Monitoring and throttling
  • Future extensibility (rate limits, logging, etc.)

We’ll keep it simple to start — routing and securing our /products endpoint.

Step 1: Add Kong to Docker Compose

Extend your existing docker-compose.yml:

  kong-db:
    image: postgres:13
    container_name: kong_postgres
    environment:
      POSTGRES_DB: kong
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kong
    ports:
      - "5433:5432"

  kong-migrations:
    image: kong/kong-gateway:3.6
    command: kong migrations bootstrap
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-db
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kong
    depends_on:
      - kong-db

  kong:
    image: kong/kong-gateway:3.6
    container_name: kong
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-db
      KONG_PG_PASSWORD: kong
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
      KONG_ADMIN_LISTEN: "0.0.0.0:8001, 0.0.0.0:8444 ssl"
    ports:
      - "8000:8000"   # Public proxy
      - "8443:8443"   # Public proxy SSL
      - "8001:8001"   # Admin API
      - "8444:8444"   # Admin API SSL
    depends_on:
      - kong-db

Then run:

docker compose up -d kong-db kong-migrations kong

Wait a few seconds for Kong to fully initialize.

Step 2: Verify Kong Admin API

Check that the Kong Admin API is accessible:

curl http://localhost:8001

You should get a JSON response with Kong’s version and configuration.

Step 3: Add Your Quarkus API as a Service

curl -i -X POST http://localhost:8001/services \
  --data name=quarkus-api \
  --data url=http://host.docker.internal:8081

host.docker.internal is used to reach Quarkus from inside the Kong container (macOS, Windows). On Linux, use 172.17.0.1 or the IP of your host.

Step 4: Add a Route for /products

curl -i -X POST http://localhost:8001/services/quarkus-api/routes \
  --data 'paths[]=/products'

You can now access your API through Kong:

curl http://localhost:8000/products

At this point, the /products endpoint is publicly accessible through Kong.

Step 5: Secure with OIDC Plugin (Optional for Now)

Kong supports token validation via the OIDC plugin, which can be configured to validate Keycloak tokens. If you'd like, I can walk you through setting that up next, or we can move on to deploying everything with Docker Compose in production.


Securing Kong Routes with Keycloak via OIDC Plugin

We'll configure Kong to:

  • Validate access tokens issued by Keycloak
  • Forward authenticated requests to your Quarkus API
  • Reject unauthorized requests

1. Ensure Prerequisites

  • Kong is running with Admin API exposed at http://localhost:8001
  • Keycloak is running, and a realm (e.g., secure-api) is set up
  • A public client (or confidential with secret) is created in Keycloak
  • Your Quarkus API works at http://localhost:8081/products

2. Test Your Keycloak Setup

From Keycloak:

1. Create a user and assign them a password.

2. Get a token using password grant:

curl -X POST http://localhost:8080/realms/secure-api/protocol/openid-connect/token \
  -d "client_id=my-client" \
  -d "grant_type=password" \
  -d "username=myuser" \
  -d "password=mypassword" \
  -d "client_secret=YOUR_SECRET" # Only for confidential clients

You should receive a JSON with an access_token.

3. Enable the OIDC Plugin on Kong’s /products route

First, get the route ID:

curl http://localhost:8001/routes

Look for the route pointing to /products and note its "id" (e.g., abc123).

Now, apply the OIDC plugin to that route:

curl -i -X POST http://localhost:8001/routes/ROUTE_ID/plugins \
  --data "name=openid-connect" \
  --data "config.issuer=http://host.docker.internal:8080/realms/secure-api" \
  --data "config.client_id=secure-api-client" \
  --data "config.client_secret=YOUR_SECRET" \
  --data "config.auth_methods=password" \
  --data "config.scopes=openid"

Use host.docker.internal so Kong (in Docker) can reach Keycloak (on host).
Use config.discovery if you want to provide full .well-known/openid-configuration.

4. Test Secured Access

Try accessing /products without a token:

curl http://localhost:8000/products

You should get: HTTP 401 Unauthorized

Now try with a token:

curl http://localhost:8000/products \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

You should get the JSON product list from Quarkus.

Optional Enhancements

  • Enforce role-based access: Add config.roles_claim and config.required_scope
  • Add token introspection instead of JWT validation
  • Secure all routes via a global plugin


Final Thoughts

Building a secure API requires careful consideration of authentication, authorization, and network architecture. In this tutorial, we combined several powerful tools—Quarkus for building reactive and efficient Java APIs, PostgreSQL as a robust relational database, Keycloak for identity and access management, and Kong as a scalable API gateway.

By the end of this setup, you’ve achieved:

  • A CRUD API implemented with Quarkus and PostgreSQL
  • Identity and token management using Keycloak and OAuth2
  • Centralized routing and security enforcement using Kong’s OIDC plugin

This architecture not only secures your API endpoints but also makes it easier to manage authentication at scale, enforce policies consistently, and prepare your application for production traffic.

In the next section, we’ll go even further by implementing role-based access control (RBAC) and rate limiting—key components for building resilient and enterprise-ready APIs.

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about Java and Spring Framework, you can take the following cheap course:

Thanks!