How to use JWT to authorise access to Google API on Mule
One of the recent demo I was working on at work required me to integrate with GSuite Admin’s Directory API. In most use-cases, working on the API’s authorisation using OAuth 2.0 is straightforward. However, since the use-case was a backend synchronisation of user details domain-wide, it makes a lot more sense for to use the server to server authorisation using service accounts. This requires you to use JWT to authorise access to Google APIs. Unfortunately, Mule does not have an out of the box component to generate JWT tokens. Thus, I needed to implement a JWT token generator with my service account credentials.
Using JWT to authorise access to Google API on Mule
You can find all the details of what’s required from the Google’s documentation on service accounts. When you create the service account, you will download a json file that provides you with the service account’s private key. You need to extract the PEM formatted private key into a file named gsuite.key as per shown below in the screenshot. My Java class in my project expects to find the file in the main/resources directory of the project. Do note that I removed the “—–BEGIN PRIVATE KEY—–” and “—–END PRIVATE KEY—–” portion of the key in the file.
You will be also a need to ensure that you set up the service account to have domain-wide authority. You can find details of that here.
For my demo, I decided to leverage on the Java JWT (JJWT) library to help me generate the JWT token. I also used the bouncycastle library to decode the PEM certificate of which I would use to create a private key that would be used to signed the JWT token.
The following is a snippet of the pom.xml dependency that I used to install those library.
<!-- for JWT generation --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.45</version> </dependency>
To keep this guide simpler, I’ve pushed my mule project in a github repository here so that I can directly zoom into the main parts of the implementation. The Mule project itself is a simple System API that provides two resources: organisation unit and users. You may find this useful but the key flow that you want to get to know is the get_bearer_token flow in the google_auth.xml configuration file.
You’ll notice that I’ve implemented the authorisation flow wrapped in a cache scope which would return the bearer token’s string. This string would essentially be used with message enricher to save the bearer token in a flow variable to be used in the API requests.
The main gist of the flow lies on the Java component that calls the GenerateJwtToken class. The class loads the GSuite service account credentials from the mule-app.properties. You can of course use a custom properties file or even use the credentials vault to store the properties securely. But since this was for a demo that would be deployed to CloudHub, mule-app.properties it is for this case,
//Get the required Google JWT properties. String issuer = eventContext.getMuleContext().getRegistry().get("googleJWT.issuer"); String scope = eventContext.getMuleContext().getRegistry().get("googleJWT.scope"); String subject = eventContext.getMuleContext().getRegistry().get("googleJWT.subject"); String audience = eventContext.getMuleContext().getRegistry().get("googleJWT.audience"); String ttlMsStr = eventContext.getMuleContext().getRegistry().get("googleJWT.ttlMs");
Google also requires the use of RS256 algorithm with its JWT. The following portion of the code shows you how I’ve created the RSA private key with the key you got with your service account.
RSAPrivateKey prikey = null; try { //Load the gsuite,key used to sign the token ClassLoader classLoader = getClass().getClassLoader(); URL gsuiteKeyResource = classLoader.getResource("gsuite.key"); File filePrivateKey = new File(gsuiteKeyResource.getFile()); FileInputStream fis = new FileInputStream(gsuiteKeyResource.getFile()); byte[] privateKey = new byte[(int) filePrivateKey.length()]; fis.read(privateKey); fis.close(); byte[] encoded = Base64.decode(privateKey); java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec privatekeySpec = new PKCS8EncodedKeySpec(encoded); prikey = (RSAPrivateKey) keyFactory.generatePrivate(privatekeySpec); ...
The last bit of the code is where you finally build the JWT.
//The JWT signature algorithm we will be using to sign the token SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.RS256; long nowMs = System.currentTimeMillis(); long expMs = nowMs + Long.parseLong(ttlMsStr); Date now = new Date(nowMs); Date exp = new Date(expMs); //Let's set the JWT Claims JwtBuilder builder = Jwts.builder() .setIssuedAt(now) .setSubject(subject) .setIssuer(issuer) .setAudience(audience) .claim("scope",scope) .signWith(signatureAlgorithm, prikey) .setExpiration(exp); //Builds the JWT and serializes it to a compact, URL-safe string return builder.compact();
And with that, you should now be able to easily authorise your Mule flows to call those Google APIs directly without any needing to redirecting to a user to authorise the access.
I hope that this simple example project and the guide above is helpful.