AWS Cognito + MP JWT RBAC + Quarkus

In this blog, we will try to build a Role-Based-Access-Control (RBAC) with Quarkus, MicroProile JWT RBAC and AWS Cognito.

AWS Cognito will create JWT token and RSA Public Key Distribution. Quarkus is responsible for Java Server-side API endpoints.

Useful links:

Eclipse MicroProfile – JWT RBAC Security (MP-JWT)

QUARKUS – USING JWT RBAC

  • Create AWS Cognito User Pool and then in this User Pool create a User and Group. Here we use “Cognito Groups” as “User Roles”.
  • Create an AWS Cognito Identity Pool and get an identity pool Id , eg "eu-central-1_xxxxx". This Cognito Identity Pool will be the JWT Issuer and we could find the RSA Publicy Key under "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_xxxxx/.well-known/jwks.json"
  • Create the endpoint by using Quarkus, for example:
@Path("/orders")
@RequestScoped
public class OrderResource {

    @GET
    @RolesAllowed({"USER", "ADMIN"})
    @Produces(MediaType.APPLICATION_JSON)
    public Response list(){
        return Response.ok(Arrays.asList("Order1", "Order2")).build();
    }
}

Most important since the default group claim in MP-JWT is “groups” but the Cognito group claim is “cognito:groups” so we need config a mapping.

smallrye.jwt.path.groups=cognito:groups

Other necessary configs:

mp.jwt.verify.publickey.location=https://cognito-idp.eu-central-1.amazonaws.com/eu-central-xxxxx.well-known/jwks.json

mp.jwt.verify.issuer=https://cognito-idp.eu-central-1.amazonaws.com/eu-central-xxxxx

quarkus.smallrye-jwt.enabled=true
quarkus.smallrye-jwt.auth-mechanism=MP-JWT
quarkus.smallrye-jwt.realm-name=Quarkus-JWT

For testing and getting a cognito jwt token you could try aws cli:

aws cognito-idp admin-initiate-auth --region eu-central-1 --cli-input-json file://auth.json

Then you put that token in the HTTP header “Authorization” and begins with “Bearer ” for example:

curl -X GET \
https://example/orders \
-H 'Authorization: Bearer YOUR_JWT_TOKEN' \

There you are the integration Quarkus + MP JWT and AWS Cognito. Enjoy!

Integration Okta with your web application

1. Introduction Okta

Okta is an amazing identity management SasS products. It provides single sign-on solution, serves as a Security Gateway and it can protect all the internal employee daily used applications.

It also provides Okta API, so you can integrate their solution into your application. For example, you can use okta authentication and authorization API to control login.

2. Create a Okta Web Application

You start by sign up in Okta Developer .  And then create a Web Application :

Specify Login redirect URIs

3. Integration Front End : okta_sign-in_widget

You could use Okta Sign-In Widget  as your login page.

var signIn = new OktaSignIn({baseUrl: 'https://dev-xxxx.oktapreview.com'});
  signIn.renderEl({
    el: '#widget-container'
  }, function success(res) {
    if (res.status === 'SUCCESS') {
      console.log('Do something with this sessionToken', res.session.token);
    } else {
    // The user can be in another authentication state that requires further action.
    // For more information about these states, see:
    //   https://github.com/okta/okta-signin-widget#rendereloptions-success-error
    }
  });
4. Integration Back End:  Implement the Authorization Code Flow

At a high-level, this flow has the following steps (copy from doc Okta):

  • Your application directs the browser to the Okta Sign-In page, where the user authenticates.
  • The browser receives an authorization code from your Okta authorization server.
  • The authorization code is passed to your application.
  • Your application sends this code to Okta, and Okta returns access and ID tokens, and optionally a refresh token.
  • Your application can now use these tokens to call the resource server (for example an API) on behalf of the user.

As mentioned in the above login widget javascript, you could get a res.session.token, you send this session token to backend controller.

Backend controller use this session token call /authorize endpoint to get a code (you might get a 302 response and in the response header you could find a location with a URI contains code param)

After that, using code to call /token endpoint to get the access and ID tokens.

play framework (filter, action)

1. KeY concepts of http architect in Play framework

In core play2, the handling of HTTP is :


RequestHeader -> Array[Byte] -> Result 
RequestHeader -> Iteratee[Array[Byte],Result]

The above computation takes the request header RequestHeader, then takes the request body as Array[Byte] and produces a Result. (play doc)

2. action and filter

Most of the requests received by a Play application are handled by an Action.

A play.api.mvc.Action is basically a (play.api.mvc.Request => play.api.mvc.Result) function that handles a request and generates a result to be sent to the client.

A Controller is nothing more than a singleton object that generates Action values. (play doc)

The Filter is applying global filters to each request.

3. Using Filter and action

The filter is used for all the routes. Action can be customized for certain route (on a specific controller)

Using Filter for creating global ratelimter for the API:


public class GlobalRatelimitFilter extends Filter {

    private static final long MAX_REQUESTS = 10;
    private static final long PERIOD = 60;
    private AtomicLong remainingRequests;
    private Instant expirationTime;

    @Inject
    public GlobalRatelimitFilter(Materializer mat) {
        super(mat);
        this.remainingRequests = new AtomicLong(MAX_REQUESTS);
        this.expirationTime = Instant.now().plusSeconds(PERIOD);
    }

    @Override
    public CompletionStage apply(Function<Http.RequestHeader, CompletionStage> next, Http.RequestHeader rh) {

        if (Instant.now().isAfter(this.expirationTime)) {
            this.remainingRequests = new AtomicLong(MAX_REQUESTS);
            this.expirationTime = Instant.now().plusSeconds(PERIOD);
        }
        if (this.remainingRequests.get() > 0L) {
            this.remainingRequests.getAndDecrement();
            return next.apply(rh).thenApply(result -> result.withHeader("X-Remaining-Ratelimit", String.valueOf(this.remainingRequests.get())));
        } else {
            return CompletableFuture.completedFuture(Results.status(429, "too many requests globally"));
        }
    }
}

code github

Using action for creating specific ratelimiter for certain routes:


public class RatelimitAction extends Action {

    private static final String USER_ENDPOINT_RATELIMIT = "user-endpoint-ratelimit";

    @Inject
    @NamedCache("ratelimit-cache")
    private SyncCacheApi cache;

    @Override
    public CompletionStage call(Http.Context ctx) {
        int limit = configuration.limit();
        int period = configuration.period();
        Ratelimit ratelimit = cache.getOrElseUpdate(USER_ENDPOINT_RATELIMIT, () -> new Ratelimit(limit, period));

        if (ratelimit.expired()) {
            ratelimit = new Ratelimit(limit, period);
            cache.set(USER_ENDPOINT_RATELIMIT, ratelimit);
        }
        ratelimit.decrease();

        if (ratelimit.reached()) {
            return CompletableFuture.completedFuture(Results.status(429, "too may requests for /users endpoint"));
        }

        return delegate.call(ctx);
    }
}

Using Action to control the API, Security, verify the requests before it calling the controller.


public class IPStrictAction extends Action {

    @Override
    public CompletionStage call(Http.Context ctx) {

        String[] whiteListIPs = configuration.whiteListIPs();

        String clientHost = ctx.request().host().split(":")[0];

        if(!iPwhitelisted(whiteListIPs, clientHost)){
            CompletableFuture.completedFuture(Results.status(403, "IP not allowed"));
        }

        return delegate.call(ctx);
    }

    private boolean iPwhitelisted(String[] whiteListIPs, String clientHost) {
        return Arrays.asList(whiteListIPs).contains(clientHost);
    }
}

Code Github

Password Store Best practice – Security Hash in Java

In Development we often encounter the case when we have to store the user’s password. Obversely, we should never store the real, raw password. Instead we store the hash value of the password.

There are some advanced Hashing secure one-way functions compute a one-way (irreversible) transform.  Owasp

Today we will introduce two widely used hash methods:

  • PBKDF2WithHmacSHA512:

PBKDF2 (Password-Based Key Derivation Function 2)
SHA (Secure Hash Algorithm )

Java implementation code

  • Bcrypt:

bcrypt is a password hashing function based on the Blowfish cipher.

Java implementation code

Other good articles:

https://howtodoinjava.com/security/how-to-generate-secure-password-hash-md5-sha-pbkdf2-bcrypt-examples/

HMAC (Hash-based message authentication codes)

HMAC stands for Hash-based message authentication codes which is often used between communication between server and client.

  1. The server and client are both provided with a secret key known only by that specific server and client.
  2. The client generates a unique HMAC, or hash, per request to the server by hashing the request data with the secret key and sending it as part of a request.
  3. The server receives the request and regenerates its own unique HMAC, it compares the two HMACs. If they’re equal, the client is trusted and the request is executed.
  4. The whole process is well described here by Amazon.
  5. RFC 2104 – HMAC: Keyed-Hashing for Message Authentication
  6. Example of codes for creating HMAC-SHA256.

Using JWT RSA in Java

JWT is used generally in two cases (according to https://jwt.io/introduction/):
Authentication  and
Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn’t been tampered with.

1.Generate JWT RS256 Private, Public Key

ssh-keygen -t rsa -b 2048 -f jwtRS256.key
# Don't add passphrase
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub

2.RSA Key Format

  • Public Key:

PKCS#1 RSAPublicKey* (PEM header: BEGIN RSA PUBLIC KEY)
X.509 SubjectPublicKeyInfo** (PEM header: BEGIN PUBLIC KEY)

  • Private Key:

PKCS#1 RSAPrivateKey** (PEM header: BEGIN RSA PRIVATE KEY)
PKCS#8 PrivateKeyInfo* (PEM header: BEGIN PRIVATE KEY)

3. Sign JWT with Private Key

Using auth0 java-jwt lib

try {
    Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
    String token = JWT.create()
        .withIssuer("auth0")
        .sign(algorithm);
} catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
}

4.Verify JWT with Public Key

Using auth0 java-jwt lib

try {
    Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
    JWTVerifier verifier = JWT.require(algorithm)
        .withIssuer("auth0")
        .build(); //Reusable verifier instance
    DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
    //Invalid signature/claims
}

Code Example Git