Validate the identity token of a user authenticated with AWS Cognito in Java

Thing you want to do

A client authenticated with AWS Amplify Authentication (https://aws-amplify.github.io/docs/js/authentication) sends an id_token to the server and validates the id_token on the Java-implemented server.

TL;DR

--Make sure that the issuer (payload iss) is the target Cognito user pool. --Signature verification is thrown to https://github.com/auth0/java-jwt. --com.auth0.jwt.JWTVerifier assumes thread safety and caches itself to reduce the number of times Cognito gets the RSA key pair used for signing.

Implementation

IdTokenValidator.java

IdTokenValidator.java


package com.exampleawsCognito.jwt;

import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPublicKey;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

import com.auth0.jwk.GuavaCachedJwkProvider;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;


/**
 *Handle id tokens obtained by authenticating with Cognito
 *
 * <p>
 *Source that was used as a reference for implementation
 * https://github.com/awslabs/cognito-proxy-rest-service/blob/master/src/main/kotlin/com/budilov/cognito/services/CognitoService.kt
 * </p>
 *
 * @ThreadSafe
 *
 */
public class IdTokenValidator {
	private static final String AWS_REGION = "ap-northeast-1";
	private static final String AWS_COGNITO_USER_POOL_ID = "my_userpool_id";

	private static JWTVerifier verifier = null;
	private static long verifierExpire = -1;
	private static final long VERIFIER_LIVE_MILISEC = 10 * 60 * 1000; //10 minutes
	private static final JWT JWT = new JWT();

	/**
	 *constructor
	 */
	public IdTokenValidator() {
	}

	/**
	 *Validate ID token
	 *
	 * @param idToken ID token to be verified
	 * @ID Token payload if return validation is successful
	 *
	 * @throws InvalidTokenException Authentication failed because the ID Token value is invalid.
	 */
	public DecodedJWT verify(String idToken) throws InvalidTokenException {
		DecodedJWT decodedToken = JWT.decodeJwt(idToken);

		//Make sure it is signed in the cognito user pool
		String iss = decodedToken.getIssuer();
		if (!jwtTokenIssuer().equals(iss)) {
			throw new InvalidTokenException("The issuer of the ID token is not the target system. iss=" + iss, idToken);
		}

		//Make sure that the ID token is used for "ID".
		String tokenUse = decodedToken.getClaim("token_use").asString();
		if (!"id".equals(tokenUse)) {
			throw new InvalidTokenException("The use of the ID token is not an ID. token_use=" + tokenUse, idToken);
		}

		//Check the signature algorithm.
		String alg = decodedToken.getAlgorithm();
		if (!"RS256".equals(decodedToken.getAlgorithm())) {
			throw new InvalidTokenException("The ID token signing algorithm does not support it. alg=" + alg, idToken);
		}

		//Validate payload and signature.
		DecodedJWT decodedJWT = null;
		if ((decodedJWT = tokenVerify(decodedToken)) == null) {
			throw new InvalidTokenException("ID Token verification failed.", idToken);
		}

		return decodedJWT;
	}

	/**
	 *Perform verification using the library of auth0
	 *
	 * @param kid ID Key ID in the token header
	 * @If return is not null, the decoded ID token
	 *
	 * @throws InvalidTokenException Validation failed
	 */
	private DecodedJWT tokenVerify(DecodedJWT jwToken) throws InvalidTokenException {

		try {
			DecodedJWT verified = getVerifier(jwToken.getKeyId()).verify(jwToken);
			return verified;
		} catch (Exception e) {
			throw new InvalidTokenException(e);
		}
	}

	/**
	 *Get an instance of JWTVerifier.
	 *
	 * <p>
	 *JWTVerifier is ver.Since it became thread safe from 3, it will be reused.
	 *However, it should be updated regularly, considering that the RSA key pair used for signing may be updated.
	 * </p>
	 *
	 * @param kid Key ID used for signing
	 *
	 * @return
	 *
	 * @throws MalformedURLException
	 * @throws JwkException
	 */
	private JWTVerifier getVerifier(String kid) throws MalformedURLException, JwkException {
		if (verifier != null && System.currentTimeMillis() < verifierExpire) {
			//If it is within the expiration date, use it as it is
			return verifier;
		}
		synchronized (JWT) {
			//I got the lock, so check it again just in case and then create the instance
			if (verifier != null && System.currentTimeMillis() < verifierExpire) {
				return verifier;
			}

			UrlJwkProvider http = new UrlJwkProvider(new URL(jwksUrl()));
			GuavaCachedJwkProvider provider = new GuavaCachedJwkProvider(http);
			Jwk jwk = provider.get(kid);

			Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
			verifier = JWT.require(algorithm)
					.withIssuer(jwtTokenIssuer())
					.build();

			//Extend the life of JWTVerifier
			verifierExpire = System.currentTimeMillis() + VERIFIER_LIVE_MILISEC;

			Calendar expire = GregorianCalendar.getInstance();
			expire.setTimeInMillis(verifierExpire);
			Logger.info("You have created an instance of JWTVerifier. Deadline"
					+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(expire.getTime()));

		}

		return verifier;

	}

	/**
	 *Get the issuer of the ID token
	 *
	 * @return
	 */
	private String jwtTokenIssuer() {
		return String.format("https://cognito-idp.%s.amazonaws.com/%s", AWS_REGION, AWS_COGNITO_USER_POOL_ID);
	}

	/**
	 *JSON web token(JWT)Get the URL of the set.
	 *
	 * @return
	 */
	private String jwksUrl() {
		return jwtTokenIssuer() + "/.well-known/jwks.json";
	}

}

InvalidTokenException.java

InvalidTokenException.class


public class InvalidTokenException extends Exception {

    public InvalidTokenException(String message, String idToken) {
        super(message + " token is " + idToken);
    }

    public InvalidTokenException(Throwable e) {
        super(e);
    }
}

User side


    try{
        //TODO IdTokenValidator is thread-safe, so make it static
        DecodedJWT payload = new IdTokenValidator().verify(idToken);
        String cognitoUserName = payload.getClaim("cognito:username").asString();

        //Required processing

    }catch (InvalidTokenException e) {
        Logger.error("Identity token validation failed", e);
        badRequest("The value of IdToken is invalid");
    }

reference

Recommended Posts

Validate the identity token of a user authenticated with AWS Cognito in Java
How to get the ID of a user authenticated with Firebase in Swift
Measure the size of a folder in Java
A quick explanation of the five types of static in Java
Find the number of days in a month with Kotlin
Find out the list of fonts available in AWS Lambda + Java
A story about hitting the League Of Legends API with JAVA
Let's create a TODO application in Java 5 Switch the display of TODO
Graph the sensor information of Raspberry Pi in Java and check it with a web browser
Dynamically increase the number of elements in a Java 2D array (multidimensional array)
Get the result of POST in Java
Get a list of S3 files with ListObjectsV2Request (AWS SDK for Java)
The story of forgetting to close a file in Java and failing
The story of making a game launcher with automatic loading function [Java]
Let's express the result of analyzing Java bytecode with a class diagram
The identity of params [: id] in rails
Sample program that returns the hash value of a file in Java
Split a string with ". (Dot)" in Java
How to get the absolute path of a directory running in Java
The story of writing Java in Emacs
Let's make a calculator application with Java ~ Create a display area in the window
Until you run a Java program with the AWS SDK local to Windows
Code that deletes all files of the specified prefix in AWS S3 (Java)
The story of low-level string comparison in Java
[Java] Handling of JavaBeans in the method chain
The story of making ordinary Othello in Java
Read a string in a PDF file with Java
Create a CSR with extended information in Java
About the idea of anonymous classes in Java
A story about the JDK in the Java 11 era
The story of learning Java in the first programming
Feel the passage of time even in Java
Calculate the similarity score of strings with JAVA
Import files of the same hierarchy in Java
A Java user over a dozen years ago tried to study the functions of Java8 (Generics).
[Java] Integer information of characters in a text file acquired by the read () method
How to save a file with the specified extension under the directory specified in Java to the list
Write a test by implementing the story of Mr. Nabeats in the world with ruby
Set a signed cookie (for CloudFront) with a custom policy using the AWS SDK for Java
[Java] Cut out a part of the character string with Matcher and regular expression
[Java] The problem that true was returned as a result of comparing Integer with ==
Summary of how to use the proxy set in IE when connecting with Java
Count the number of occurrences of a string in Ruby
Submit a job to AWS Batch with Java (Eclipse)
CI the architecture of Java / Kotlin applications with ArchUnit
A quick review of Java learned in class part4
The story of making a reverse proxy with ProxyServlet
Monitor the internal state of Java programs with Kubernetes
Check the behavior of Java Intrinsic Locks with bpftrace
A note for Initializing Fields in the Java tutorial
[Java] Get the file in the jar regardless of the environment
Quickly implement a singleton with an enum in Java
[Java] Get the file path in the folder with List
[Java] When writing the source ... A memorandum of understanding ①
Output true with if (a == 1 && a == 2 && a == 3) in Java (Invisible Identifier)
A quick review of Java learned in class part3
A quick review of Java learned in class part2
Change the storage quality of JPEG images in Java
A survey of the Kubernetes native Java framework Quarkus
The story of making dto, dao-like with java, sqlite
Summarize the additional elements of the Optional class in Java 9