25

I am using aws cognito user pool, after user signed in, I got an id token at my single page application, which is expected, then for each request, I need to verify the id token at my backend rest API, which is in java, the aws doc didn't mention too much about how to do it.

Is there any example for it?

Confusions include:

  1. the id token seems not just a signed JWT, it's also encrypted, when use nimbus library, I need to specify a secret for an encrypted JWT, where can I get the secret? my understanding is this should come from aws, do I needed to download something and then put in my jvm keystore?

  2. there is a well-known jwts.json can be downloaded from aws, it looks like:

`

{
    "keys": [
        {
            "alg": "RS256",
            "e": "AQAB",
            "kid": "HFPWHdsrG5WyulOwH5dai69YTsWz2KBB1NHbAcVx7M0=",
            "kty": "RSA",
            "n": "...",
            "use": "sig"
        },
        {
            "alg": "RS256",
            "e": "AQAB",
            "kid": "kSwTdVq/qD4Ra4Q8dJqUTlvOA7eiLxezOZ3mJKI61zU=",
            "kty": "RSA",
            "n": "....",
            "use": "sig"
        }
    ]
}

`

how to understand this, what does each property used for? is that every user in the user pool represents one key?

  1. Is there any example java code for the aws cognito service verification, can I use aws sdk or I have to use library like nimbus to do the verification on my own?
Jakim
  • 1,713
  • 7
  • 20
  • 44

3 Answers3

56

I just struggled with this and thought I share it.

If you use maven add this to your pom.xml

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>jwks-rsa</artifactId>
    <version>0.4.0</version>
</dependency>

If you use gradle add

compile 'com.auth0:jwks-rsa:0.4.0'
compile 'com.auth0:java-jwt:3.3.0'

Create a class that implements RSAKeyProvider

import com.auth0.jwk.JwkException;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.interfaces.RSAKeyProvider;

import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

public class AwsCognitoRSAKeyProvider implements RSAKeyProvider {

    private final URL aws_kid_store_url;
    private final JwkProvider provider;

    public AwsCognitoRSAKeyProvider(String aws_cognito_region, String aws_user_pools_id) {
        String url = String.format("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", aws_cognito_region, aws_user_pools_id);
        try {
            aws_kid_store_url = new URL(url);
        } catch (MalformedURLException e) {
            throw new RuntimeException(String.format("Invalid URL provided, URL=%s", url));
        }
        provider = new JwkProviderBuilder(aws_kid_store_url).build();
    }


    @Override
    public RSAPublicKey getPublicKeyById(String kid) {
        try {
            return (RSAPublicKey) provider.get(kid).getPublicKey();
        } catch (JwkException e) {
            throw new RuntimeException(String.format("Failed to get JWT kid=%s from aws_kid_store_url=%s", kid, aws_kid_store_url));
        }
    }

    @Override
    public RSAPrivateKey getPrivateKey() {
        return null;
    }

    @Override
    public String getPrivateKeyId() {
        return null;
    }
}

Now you can verify your token by

String aws_cognito_region = "us-east-1"; // Replace this with your aws cognito region
String aws_user_pools_id = "us-east-1_7DEw1nt5r"; // Replace this with your aws user pools id
RSAKeyProvider keyProvider = new AwsCognitoRSAKeyProvider(aws_cognito_region, aws_user_pools_id);
Algorithm algorithm = Algorithm.RSA256(keyProvider);
JWTVerifier jwtVerifier = JWT.require(algorithm)
    //.withAudience("2qm9sgg2kh21masuas88vjc9se") // Validate your apps audience if needed
    .build();

String token = "eyJraWQiOiJjdE.eyJzdWIiOiI5NTMxN2E.VX819z1A1rJij2"; // Replace this with your JWT token
jwtVerifier.verify(token);

Note that JwkProviderBuilder will build a JwkProvider with a LRU cache that caches keys retreived from the aws key store which is quite neat! The cache rules can be change with the builder.

[UPDATE] Moved creation JwkProvider to constructor so caching is respected as @danieln commented

AndiDev
  • 1,272
  • 14
  • 26
  • 1
    That is absolutely brilliant, I've spent hours trying to implement this from different angles. No solution I've found so far is as simple and straight forward as this one. Works like a charm! Thanks a lot, mate. – Double M Apr 25 '20 at 12:11
  • Thanks a lot, finally I got it working. Two small things I had to do was getting newer versions of the jwt libraries and make sure the JWT token was stripped of CR LF. – Per Digre May 06 '20 at 07:42
  • @AndiDev Great Answer! Shouldn't you initialize JwkProvider once and just return it in getPublicKeyById rather than initializing it over and over? – danieln Dec 07 '20 at 18:33
  • @danieln good point. Feels like it JwkProvider provider should be created in the constructor. Otherwise the caching does not work I guess. Don't use this code anymore so I cant verify it. If you can verify it works as intended I can change the answer. – AndiDev Dec 07 '20 at 21:08
  • I verified it and it's working as expected. – danieln Dec 09 '20 at 15:32
0

As for the secret, are you referring the App Client specific one? This you get when you create and App Client. Go to the AWS Console and Cognito. Choose the appropriate User Pools, click on App Client. There is the secret, but you have to make sure you select the option to create it (or just don't use one) when you create the App Client. Otherwise, make a new one.

chris2187
  • 51
  • 3
  • also, here is an example, however it is old and there are several deprecated items it will give you an ideahttps://aws.amazon.com/blogs/developer/building-a-serverless-developer-authentication-api-in-java-using-aws-lambda-amazon-dynamodb-and-amazon-cognito-part-2/ or here https://github.com/awslabs/aws-cognito-java-desktop-app – chris2187 Jan 20 '18 at 18:06
  • See this related post https://stackoverflow.com/questions/40302349/how-to-verify-jwt-from-aws-cognito-in-the-api-backend?rq=1 – chris2187 Jan 20 '18 at 18:17
-5

You can verify the token using a standard JWT library. In addition, there are several steps involved in verifying the JWT Token. Though I couldn't find a Java Example, the following is a NodeJS example which will explain the verification process.

const jwt = require('jsonwebtoken');
const jwtToken = "sampletoken****";
const jwkPem = { "alg" : "RS256", "kid" : "samplekid****" }

var decodedJwt = jwt.decode(jwtToken, {complete: true});

//Fail if the token is not jwt
if (!decodedJwt) {
    console.log("Not a valid JWT token");
    return;
}

//Fail if token is not from your User Pool
if (decodedJwt.payload.iss != iss) {
    console.log("invalid issuer");
    return;
}

//Reject the jwt if it's not an 'Access Token'
if (!(decodedJwt.payload.token_use == 'id' || 
    decodedJwt.payload.token_use == 'access')) {
    console.log("token_use is invalid");
    return;
}

//Get the kid from the token and retrieve corresponding PEM
var kid = decodedJwt.header.kid;
var pem = jwkPem[kid];
if (!pem) {
    console.log("Invalid access token");
    return;
}

//Verify the signature of the JWT token to ensure it's really coming from your User Pool and that it has not expired
jwt.verify(jwtToken, pem, { issuer: iss, maxAge: 3600000}, function(err, payload) {
  if(err) {
    console.log(err);
  } else {
    console.log("Authorization successful");
  }
});
Ashan
  • 18,898
  • 4
  • 47
  • 67