Skip to main content

Standalone Role-Based Access Control (RBAC) using JWT data in OPA

In this section, you will see how basic RBAC can be implemented on JSON Web Tokens (JWT) alone, in OPA.

Explanation

Two mappings need to be created to implement RBAC on top of a "subject, action, resource" model:

  1. Roles abstract actions and resources: a role consists of a set of permissions.
  2. Subjects are assigned roles: each subject of the system can be assigned one or more roles.

The minimal amount of services needed to integrate OPA for RBAC with static roles are:

  • an authentication mechanism that provides stable identifiers for sign-in users (IDs, or email/usernames)
  • a roles-service that can map subjects to roles

JWT-only RBAC

With JSON Web Token (JWT)-only RBAC, the user's roles are encoded into the JWT.

First, when an unauthenticated user attempts to perform an action on a resource, they are redirected to the authentication flow. This gives them back a JWT to send with every subsequent request.

With JWT-only RBAC, the user's roles are encoded into the JWT. For example, a decoded JWT could carry claims like these:

{
"name": "Alicia Smithsonian",
"roles": [
"reader",
"writer"
],
"username": "alice"
}

Reading JWT claims from Rego

With the input schema as laid out in Getting Started, we'll be able to retrieve claims from the JWT as follows:

warning

We're only decoding the claims from the JWT here, without verifying the JWT's signature, expiry, etc. For a full example of decoding and verifying the JWT, see OPA by Example on io.jwt.decode_verify().

Failing to verify a JWT could expose your application to unauthorized access from even low-skilled adversaries.

Example input:

{
"resource": {
"type": "endpoint",
"id": "/documents/1"
},
"action": {
"name": "GET",
"protocol": "HTTP 1.1",
"headers": {
":authority": "example-app",
":method": "POST",
":path": "/pets/dogs",
"accept": "*/*",
"authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQWxpY2lhIFNtaXRoc29uaWFuIiwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInVzZXJuYW1lIjoiYWxpY2UifQ.md2KPJFH9OgBq-N0RonGdf5doGYRO_1miN8ugTSeTYc",
"content-length": "0",
"user-agent": "curl/7.68.0-DEV",
"x-ext-auth-allow": "yes",
"x-forwarded-proto": "http",
"x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a"
}
},
"subject": {
"type": "user",
"id": "bob"
},
"context": {
"type": "http",
"host": "example-app"
}
}
package roles

import rego.v1

user_roles contains role if {
hdr := substring(input.action.headers.authorization, 7, -1)
[_, claims, _] := io.jwt.decode(hdr)
some role in claims.roles
}

References