Configuring OpenId Authorization with Keycloak

OpenID Connect is a widely-used authentication protocol that allows users to authenticate themselves to a relying party (RP) website or application using their existing credentials from an identity provider (IDP). While OpenID Connect is primarily an authentication protocol, it also provides some support for authorization through the use of client resources and policies. In this article, we’ll take a closer look at OpenID Connect client authorization and why it matters.

What Is OpenID Connect Client Authorization?

OpenID Connect client authorization refers to the process of granting a client application access to protected resources on behalf of a user. In order for a client to access protected resources, it needs to be authorized to do so by the IDP through the following elements:

  1. Permissions: Refers to the actions that an authenticated user is able to perform on a resource.
  2. Resources: Refers to the assets that the client is requesting access to.
  3. Policies: Refers to the rules that govern access to a resource. These can be defined by the resource owner or the authorization server, and they specify which clients are allowed to access which resources and under what conditions.

Why Does OpenID Connect Client Authorization Matter?

OpenID Connect client authorization is important for several reasons.

First, it allows clients to access protected resources in a secure and controlled manner. By using client scopes and policies, the IDP can ensure that clients only have access to the resources that they need, and that access is granted based on a set of defined criteria.

Second, OpenID Connect client authorization provides a standardized way to manage authorization across multiple applications and services. By using a single IDP to manage authentication and authorization, organizations can simplify their security infrastructure and reduce the risk of security breaches.

Finally, OpenID Connect client authorization can help organizations comply with data protection regulations such as GDPR and CCPA. By using client scopes and policies to control access to personal data, organizations can ensure that they are only collecting and processing data that is necessary for a specific purpose, and that access is granted only to authorized parties.

In the next part of this article we will show how to configure a KeyCloak Realm which uses OpenId Authorization.

Configuring a Realm with OpenId Connect

Firstly, start Keycloak and access the Admin Console and create a new Realm.

Within this Realm, add the following Roles:

  • Role “hr
  • Role “dev

Then, define two Users:

  • User “joe
  • User “alice

Next, assign each user to a Role. In our example:

  • Assign Role “hr” to User “alice
  • Assign Role “dev” to User “joe

Finally, define a new Client for your Realm. Within the Client, define the redirect URI to your application and enable Authorization as part of the Capability config:

Configuring Authorization

In this part, we will configure the Authorization so that Clients connecting to KeyCloak Realm will use the Policies granted for each Role and for a specific Resources

Firstly, define the Server Resources that we want to authorize. From the Authorization | Resources Panel create the following Resources with an URI Path:

  • Dev Resource refers to the URI /api/dev/*
  • HR Resource refers to the URI /api/hr/*

Next, from the Clients Panel select Authorization | Policies and create the following “Role” Policies:

Next, bind each Policy to a Role. Bind the “Dev Policy” to the “dev” Role:

Then, bind the “HR Policy” to the “hr” Role:

Then, in the Authorization | Permissions Panel create the following Resource Based Permissions:

  • Dev Permission Bound to the “Dev” policy and the “Dev” Resource
  • HR Permission Bound to the “HR” Policy and the “HR” Resource

Finally, check from the Authorization | Resources Panel that all resources contain the correct Permissions :

Testing OpenId Connect Authorization

To test your Client configuration all you need is an application which exposes the above Endpoint Resources. You don’t need to add any @RolesAllowed annotation in your Services as Keycloak will manage the Authorization part.

Therefore, we will just define the following Rest Endpoints:

@Path("/api/dev")
public class DevEndpoint {

    @Inject
    SecurityIdentity securityContext;

    @GET
    public String access() {
        String userName = securityContext.getPrincipal().getName();
        StringBuffer buffer = new StringBuffer();
        buffer.append("Username: "+userName);
        buffer.append("\nRoles: ");
        Set<String> roles = securityContext.getRoles();

        roles.forEach(role -> buffer.append(role).append("\n"));
        return buffer.toString();
    }
}

The first endpoint is available at the “/api/dev” URI and requires a “dev” user.

The first endpoint is available at the “/api/hr” URI and requires a “hr” user.

@Path("/api/hr")
public class HREndpoint {
    @Inject
    SecurityIdentity securityContext;

    @GET
    public String access() {
        String userName = securityContext.getPrincipal().getName();
        StringBuffer buffer = new StringBuffer();
        buffer.append("Username: "+userName);
        buffer.append("\nRoles: ");
        Set<String> roles = securityContext.getRoles();

        roles.forEach(role -> buffer.append(role).append("\n"));
        return buffer.toString();
    }
}

You can test the above Endpoint with a simple QuarkusTest which:

  • Collects the Token for each User
  • Connects to both Endpoints using the correct Token
@QuarkusTest
public class PolicyEnforcerTest {

    @ConfigProperty(name = "quarkus.oidc.auth-server-url")
    String keycloakURL;

    @Test
    public void testHRResource() throws Exception {

        String hrToken = getToken("alice","password");

        RestAssured.baseURI = "http://localhost:8081";
        RestAssured.given().auth().oauth2(hrToken)
                .when().get("/api/hr")
                .then()
                .statusCode(200);
    }

    @Test
    public void testDevResource() throws Exception {

        String hrToken = getToken("joe","password");

        RestAssured.baseURI = "http://localhost:8081";
        RestAssured.given().auth().oauth2(hrToken)
                .when().get("/api/dev")
                .then()
                .statusCode(200);
    }

    String getToken(String username, String password) throws Exception {
       String secret ="secret";
       RestAssured.baseURI = keycloakURL;
       Response response = given().urlEncodingEnabled(true)
              .auth().preemptive().basic("quarkus-client", secret)
              .param("grant_type", "password")
              .param("client_id", "demorealm-client")
              .param("username", username)
              .param("password", password)
              .header("Accept", ContentType.JSON.getAcceptHeader())
              .post("/protocol/openid-connect/token ")
              .then().statusCode(200).extract()
              .response();

       ObjectMapper objectMapper = new ObjectMapper();
       JsonNode rootNode = objectMapper.readTree(response.getBody().asString());
       return rootNode.get("access_token").asText();
    }
}

You can check the source code for this article here: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/openid-authorization

Conclusion

OpenID Connect client authorization is a powerful tool for managing access to protected resources in a secure and controlled manner. By using client scopes and policies, organizations can ensure that clients only have access to the resources that they need, and that access is granted based on a set of defined criteria. If you’re using OpenID Connect for authentication, it’s worth considering how you can also leverage its authorization capabilities to further enhance your security posture.

Found the article helpful? if so please follow us on Socials