Security using OpenID Connect

Securing endpoints using Bearer Token Authorization

You can protect your JAX-RS microservices by using Bearer Token Authorization where Bearer Tokens are issued by OpenId Connect and OAuth 2.0 compliant Authorization Servers such as Keycloak.

In this section we will secure our endpoints with Bearer Tokens by using Dev Services when running in Dev Mode and, lastly, by configuring the KeyCloak server for all the run modes.

Add the OIDC and KeyCloak extensions

Just open a new terminal window, and make sure you’re at the root of your tutorial-app project, then run:

  • Maven

  • Quarkus CLI

./mvnw quarkus:add-extension -D"extensions=quarkus-oidc,quarkus-keycloak-authorization"
quarkus extension add quarkus-oidc quarkus-keycloak-authorization

Working with DevServices for KeyCloak

Quarkus introduced an experimental Dev Services For Keycloak feature which is enabled by default when the quarkus-oidc extension is started in dev mode. It starts a Keycloak container and initializes it by registering the existing Keycloak realm or creating a new realm with the client and users for you to start developing your Quarkus application secured by Keycloak immediately.

When working in Dev Mode, you can use Dev Services for KeyCloak. This is an easy way to locally test your KeyCloak authentication.

Let’s define a static port for the KeyCloak dev service so you can copy/paste the links further down in this section. Add the following line to your application.properties:

quarkus.keycloak.devservices.port=34000

Now go to the Dev UI - Dev Services. You will see a 'keycloak' section with information about the running Keycloak Dev Services container, including auth server url and some default oidc users.

By default, alice and bob users (with the passwords matching the names), and user and admin roles are created. alice has both admin and user roles, and bob has just the user role.

We can add our own user, role and group in KeyCloak by following these steps:

  1. Go to the KeyCloak Administration console and use `admin' as user and password.

  2. Go to https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus-realm.json and save it on your local machine.

  3. Go to the 'quarkus' Realm Settings

  4. Click on 'Action' in the top right and select Partial Import.

  5. Browse to the quarkus-realm.json you downloaded and select all resources to import. Then also select "Overwrite" when it asks "If a resource already exists, specify what should be done:" After this step you should have a Subscriber Role and Group.

  6. Go to 'Users' in the left menu and click on 'jdoe' and then on the 'Role Mapping' tab and check that the jdoe user is correctly mapped to the 'Subscriber' role.

  7. Reset the password for the 'jdoe' user in the Credentials settings to 'jdoepass'.

Congratulations! Now you can run this section in Dev Mode without starting the KeyCloak container nor adding the KeyCloak setup in application.properties.

Dev Services For Keycloak will not be activated if either quarkus.oidc.auth-server-url is already initialized or the default OIDC tenant is disabled with quarkus.oidc.tenant.enabled=false, regardless if you work with Keycloak or not.

If you would like to disable Dev Services For Keycloak, just add quarkus.keycloak.devservices.enabled=true in application.properties.

Create UserResource

If you need to access JsonWebToken claims, you can simply inject the token itself.

Create the UserResource Java class in the com.redhat.developers package with the following contents:

package com.redhat.developers;

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("api/users")
public class UserResource {

    @Inject
    JsonWebToken jwt;

    @GET
    @Path("info")
    @RolesAllowed("Subscriber") (1)
    public String getInfo() {
        return "Access for subject " + jwt.getName() + " is granted";
    }

}
1 The endpoint is accessible only to users that have Subscriber role.

Invoke the /api/users/info endpoint with RBAC

First you need a token valid to authenticate. Run the following command to obtain an access token:

curl -X POST "http://localhost:34000/realms/quarkus/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "username=jdoe" -d "password=jdoepass" -d "grant_type=password" -d "client_id=admin-cli"
1 Replace the KeyCloak port with the current value of the dev service container port (this is the same as the one in the url you were using to set up KeyCloak).

You should see an output similar to:

{"access_token":"eyJhbGciOiJSUzI......","token_type":"Bearer","not-before-policy":0,"session_state":"84349a48-55ea-4c25-88cd-d26a775c8c67","scope":"email profile"}

You can store the access token in a variable and use it further for querying. Below you can find details on how to do that using curl and jq:

token=$(curl -X POST "http://localhost:34000/realms/quarkus/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "username=jdoe" -d "password=jdoepass" -d "grant_type=password" | jq -r ".access_token")

curl -H "Authorization: Bearer $token" localhost:8080/api/users/info

And you’ll see the response for the given token:

Access for subject jdoe is granted

Access UserResource with an invalid token

Run the following command:

token=$(curl https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.token -s)
curl -v -H "Authorization: Bearer $token" localhost:8080/api/users/info

And you’ll see the 401 Forbidden response.

*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /secure/claim HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg
>
< HTTP/1.1 401 Unauthorized
< www-authenticate: Bearer
< content-length: 0
<
* Connection #0 to host localhost left intact
* Closing connection 0

Add incorrect RBAC to UserResource

package com.redhat.developers;

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.jwt.JsonWebToken;

@Path("/api/users")
public class UserResource {

    @Inject
    JsonWebToken jwt;

    @GET
    @Path("/info")
    @RolesAllowed("Not-Subscriber")
    public String getInfo() {
        return "Access for subject " + jwt.getName() + " is granted";
    }

}

Invoke the endpoint with incorrect RBAC

Run the following command:

token=$(curl https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.keycloak.jwt.token -s)
curl -v -H "Authorization: Bearer $token" localhost:8080/api/users/info

And you’ll see the 403 Forbidden response.

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /secure/claim HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
> Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg
>
< HTTP/1.1 403 Forbidden
< Content-Length: 9
< Content-Type: application/octet-stream
<
* Connection #0 to host localhost left intact
Forbidden* Closing connection 0

Application Configuration

Although Dev Services are very useful when running Quarkus in Dev Mode, we need to think forward on how the application configuration will be available for production. This section explains how to persist the security configurations done earlier with Dev Services.

OpenID Connect extension allows you to define the adapter configuration using the application.properties file which should be located at the src/main/resources directory. You can simply copy the configuration below to start working with the KeyCloak server:

# OIDC Configuration

quarkus.oidc.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc.client-id=backend-service
quarkus.oidc.credentials.secret=secret
quarkus.oidc.tls.verification=none
quarkus.http.cors=true

# Enable Policy Enforcement
quarkus.keycloak.policy-enforcer.enable=true

Starting and Configuring the Keycloak Server

You can start a Keycloak Server with Docker by running the following command:

docker run --name keycloak -e DB_VENDOR=H2 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak:15.0.2

Go to http://localhost:8180/auth and use admin for user and password. We will add our own user, role and group by following these steps:

  1. Click on Import.

  2. Import the realm from https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus-realm.json. After this step you should have Subscriber Role and Group, together with jdoe user correctly configured.