Security with JWT RBAC
Securing endpoints with JWT RBAC
In a microservices architecture, and generally speaking, any application, might need to be protected so only specific users can access the defined endpoint. Quarkus provides integration to the MicroProfile JWT RBAC spec.
So let’s see how you can start using JWT for Role Based Access Control (RBAC) of endpoints.
Add the JWT properties
Add the following properties to your application.properties
in src/main/resources
:
mp.jwt.verify.publickey.location=https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.pub
mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac
#set jwt expiration duration
#com.developers.redhat.jwt.duration=3600
We are providing a valid token that can be verified by the configured public key:
{
"kid": "/privateKey.pem",
"typ": "JWT",
"alg": "RS256"
},
{
"sub": "jdoe-using-jwt-rbac",
"aud": "using-jwt-rbac",
"upn": "jdoe@quarkus.io",
"birthdate": "2001-07-13",
"auth_time": 1570094171,
"iss": "https://quarkus.io/using-jwt-rbac", (1)
"roleMappings": {
"group2": "Group2MappedRole",
"group1": "Group1MappedRole"
},
"groups": [ (2)
"Echoer",
"Tester",
"Subscriber",
"group2"
],
"preferred_username": "jdoe",
"exp": 2200814171,
"iat": 1570094171,
"jti": "a-123"
}
1 | The issuer you set in application.properties |
2 | groups field is used by MicroProfile JWT RBAC to get the access groups (or roles) that the owner of the token has |
Create SecureResource
You can inject any defined claim into an object by using @Claim
annotation:
Change the SecureResource
Java class in src/main/java
in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.Claims;
@Path("secure")
public class SecureResource {
@Claim(standard = Claims.preferred_username)
String username;
@GET
@Path("claim")
public String getClaim() {
return username;
}
}
Invoke the /secure/claim endpoint
Run the following command:
token=$(curl https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.token -s)
curl -H "Authorization: Bearer $token" localhost:8080/secure/claim
You should see the preferred_username
field for the given token (jdoe).
jdoe
MicroProfile JWT RBAC spec is providing out-of-the-box validation of the given token. These validations include, for example, that the token has not been modified, has not expired, or the issuer is the expected one.
To validate this, just invoke the service again, but change the token:
token=XXXX
curl -v -H "Authorization: Bearer $token" localhost:8080/secure/claim
* 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 XXXX
>
< HTTP/1.1 401 Unauthorized
< www-authenticate: Bearer {token}
< content-length: 0
<
* Connection #0 to host localhost left intact
* Closing connection 0
You can check the 401 Unauthorized
response.
Add RBAC to SecureResource
So far, you’ve seen how to get claims from the provided JWT token, but anyone could access that endpoint, so let’s protect it with a role.
For this case you need to use a role that is defined in the JWT token inside the groups
claim (ie Subscriber
).
Change the SecureResource
Java class in src/main/java
in the com.redhat.developers
package with the following contents:
package com.redhat.developers;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.Claims;
@Path("/secure")
public class SecureResource {
@Claim(standard = Claims.preferred_username)
String username;
@RolesAllowed("Subscriber")
@GET
@Path("/claim")
public String getClaim() {
return username;
}
}
Invoke the /secure/claim endpoint with RBAC
Run the following command:
token=$(curl https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.token -s)
curl -H "Authorization: Bearer $token" localhost:8080/secure/claim
And you’ll see the preferred_username field for the given token (jdoe).
jdoe
Add incorrect RBAC to SecureResource
package com.redhat.developers;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.Claims;
@Path("/secure")
public class SecureResource {
@Claim(standard = Claims.preferred_username)
String username;
@RolesAllowed("Not-Subscriber")
@GET
@Path("/claim")
public String getClaim() {
return username;
}
}
Invoke the /secure/claim endpoint with incorrect RBAC
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/secure/claim
And you’ll see the preferred_username field for the given token (jdoe).
* 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
You can notice the 403 Forbidden
response.