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

mvn quarkus:add-extension -Dextensions=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 the easy way to locally test your KeyCloak authentication.

Simply go to Dev UI and select the OpenId Connect Card linking to a Keycloak page.

Click on the Provider: Keycloak link and you will see a Keycloak page which will be presented slightly differently depending on how Dev Services for Keycloak feature has been configured.

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.

However, we can add our own user, role and group by following these steps:

  1. Click on KeyCloak Admin link in the left corner.

  2. Login using `admin' as user and password.

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

  4. Go to Import in the KeyCloak Administration console and import quarkus-realm.json. After this step you should have Subscriber Role and Group.

  5. Go to Users and check that jdoe user is mapped to the Subscriber role and group.

  6. Setup a password for jdoe user.

Congratulations! Now you can run this section in Dev Mode without starting the KeyCloak docker 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 javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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:56006/auth/realms/quarkus/protocol/openid-connect/token' \ (1)
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "username=jdoe" \
 -d 'password=the_pass_you_set' \ (2)
 -d 'grant_type=password' \
 -d 'client_id=admin-cli'
1 If you are using KeyCloak Dev Services, you can get the KeyCloak port from Quarkus logs.
2 Consider to replace the password here with the one you set in the beginning.

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:56006/auth/realms/quarkus/protocol/openid-connect/token' \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "username=jdoe" \
 -d 'password=jdoe' \
 -d 'grant_type=password' \
 -d 'client_id=admin-cli' | 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 javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.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.