Validate JWT token

Validate Age Estimation Token

The Age Estimation Token is a JWT token that contains the estimated age of the user.

The token is either signed, or signed and encrypted by the PRIVO ID Service. If the JWT is both signed and encrypted, the JSON document will be signed then encrypted, with the result being a Nested JWT, as defined in [RFC7519].

The subject claim (sub) contains a JSON encoded array with a few successful attempts of the estimated user age.

Example of the Age Estimation Token:

Encoded token: 
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImlkMSJ9.eyJzdWIiOiJbMzksNDAsMzksMzgsMzVdIiwiYXpwIjoicHJpdm9sb2NrIiwiYXVkIjoiQWdlRXN0aW1hdGlvbiIsImV4cCI6MTcxMDg3MDAwMSwiaWF0IjoxNzEwODY5MTAxLCJpc3MiOiJodHRwczovL2lkLXN2Yy1pbnQucHJpdm8uY29tIiwianRpIjoiZmQwOTMyYjktYzI4NS00N2NkLTliYmEtMjkwYmI1M2M5MDI0In0.T6Nat3VVkmP9X_TQ9HX08YQTRVsf_phA_KserAscuzYg25SahEANogwSriIStJ8KCiLkqsErXa77jABB65ydyINtIOJOTBNj-ks_JZsdGl9s75hrzbkZ4MfFy7_U_gL7H0NmMzrJWpUU4cNXI9oKdanZsVwIstHPE-GHc7TnwnxHffmPL7vccL_GcnmSYGWI2PRPK5Lgn7ficQZ3avJKZYEGY-vnrEYlwglEtBOcrfxow2l-8T0Oieuylrmf5LVFjSj7v8HDfEx0iU3B1i9ffZsVtCLpVwFyRgHoSiwbs0wXdtcMD_uOAVXBhMfclNBJV4uDR-_hF4kI7pnsRu0IlQ

Decoded header:
{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "id1"
}

Decoded payload:
{
  "sub": "[28,29,29,31,30]", // Estimated age, each number represents a different attempt
  "azp": "service_identifier", // Service Identifier provided by PRIVO during integration
  "aud": "AgeEstimation", // PRIVO product ID
  "exp": 1710837509, // Expiration time
  "iat": 1710836609, // Issue time 
  "iss": "https://id-svc-int.privo.com", // Issuer
  "jti": "361140de-b74f-45eb-bc06-c51a209862b2" // JWT ID
}

To verify that the Age Estimation Token is valid, ensure that the following criteria are satisfied:

  • The Age Estimation Token is properly signed by the PRIVO ID Service. Use the ID Service's public keys available in JWK format in INT env at https://id-svc-int.privo.com/.well_known/jwks to verify the token's signature. Please note that public keys depend on Enviroment (INT/PROD) that you are usign. PROD version - https://id-svc.privo.com/.well_known/jwks.
  • The value of the authorized party (azp) claim must be equal to your Service Identifier (provided by PRIVO).
  • The value of the audience claim (aud) must be equal to one of the PRIVO product IDs (AgeGate, AgeVerification, AgeEstimation, etc) that you integrate.
  • The value of the issuer (iss) claim must be equal to https://id-svc.privo.com on production enviroment or https://id-svc-int.privo.com on integration enviroment.
  • The value of the issue at (iat) claim is the number of seconds from 1970-01-01T00:00:00Z as measured in UTC, and must be less than the current time.
  • The value of the expiration time (exp) claim is the number of seconds from 1970-01-01T00:00:00Z as measured in UTC, and must be greater than the current time.

We strongly recommend using a well-known JWT library to perform the above verification steps. If you don't use any JWT in your codebase, you can find available libraries for your language at https://jwt.io/libraries.

Age Verification Token validation - Java example (JAVA-JWT)

Please see documentation and more examples at:

pom.xml

	...
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>4.4.0</version>
    </dependency>
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>jwks-rsa</artifactId>
      <version>0.22.1</version>
    </dependency>
	...

Java code

import java.net.URL;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.concurrent.TimeUnit;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.JwkProviderBuilder;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;

	...

    public static final String SERVICE_IDENTIFIER = "...";
    public static final String PRIVO_PRODUCT_ID = "AgeEstimation";

    // INT environment
    public static final String PRIVO_ID_ISSUER = "https://id-svc-int.privo.com";
    public static final String PRIVO_ID_JWKS_URL = "https://id-svc-int.privo.com/.well_known/jwks";

    // PROD environment
    // public static final String PRIVO_ID_ISSUER = "https://id-svc.privo.com";
    // public static final String PRIVO_ID_JWKS_URL = "https://id-svc.privo.com/.well_known/jwks";

	...
        // build key provider from JWKS URL
        JwkProvider provider = new JwkProviderBuilder(new URL(PRIVO_ID_JWKS_URL))
                // allow 5 cached keys for 1 hour
                .cached(5, 1, TimeUnit.HOURS).build();

        RSAKeyProvider keyProvider = new RSAKeyProvider() {
            @Override
            public RSAPublicKey getPublicKeyById(String kid) {
                try {
                    return (RSAPublicKey) provider.get(kid).getPublicKey();
                } catch (Exception e) {
					// FIXME: handle exception
                    e.printStackTrace();
                    return null;
                }
            }

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

            @Override
            public String getPrivateKeyId() {
                return null;
            }
        };
        Algorithm algorithm = Algorithm.RSA256(keyProvider);
        // build the verifier, it can be reused
        JWTVerifier verifier = JWT.require(algorithm)
                // The value of the issuer (`iss`) claim must be equal to `https://id-svc.privo.com` on production
                // enviroment or `https://id-svc-int.privo.com` on integration enviroment.
                .withIssuer(PRIVO_ID_ISSUER)
                // The value of the authorized party (`azp`) claim must be equal to your Service Identifier
                // (provided by PRIVO).
                .withClaim("azp", SERVICE_IDENTIFIER)
                // The value of the audience claim (`aud`) must be equal to one of the PRIVO product IDs (AgeGate,
                // AgeVerification, AgeEstimation, etc) that you integrate.
                .withAudience(PRIVO_PRODUCT_ID)
                // build the verifier
                .build();
		...

        // verify the token and get the estimated ages
        try {
            DecodedJWT decodedJWT = verifier.verify(ageEstimationToken);
            String ages = decodedJWT.getSubject();
            System.out.println(ages); // [39,40,39,38,35]
        } catch (JWTVerificationException ex) {
            //FIXME: handle exception
            ex.printStackTrace();
        }