JWTVerifierTest.java

package com.auth0.jwt;

import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.function.BiPredicate;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;

public class JWTVerifierTest {

    private final Clock mockNow = Clock.fixed(Instant.ofEpochSecond(1477592), ZoneId.of("UTC"));
    private final Clock mockOneSecondEarlier = Clock.offset(mockNow, Duration.ofSeconds(-1));
    private final Clock mockOneSecondLater = Clock.offset(mockNow, Duration.ofSeconds(1));

    @Rule
    public ExpectedException exception = ExpectedException.none();

    @Test
    public void shouldThrowWhenInitializedWithoutAlgorithm() {
        IllegalArgumentException e = assertThrows(null, IllegalArgumentException.class, () ->
                JWTVerifier.init(null));
        assertThat(e.getMessage(), is("The Algorithm cannot be null."));
    }

    @Test
    public void shouldThrowWhenAlgorithmDoesntMatchTheTokensAlgorithm() {
        AlgorithmMismatchException e = assertThrows(null, AlgorithmMismatchException.class, () -> {
            JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC512("secret")).build();
            verifier.verify("eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCJ9.s69x7Mmu4JqwmdxiK6sesALO7tcedbFsKEEITUxw9ho");
        });
        assertThat(e.getMessage(), is("The provided Algorithm doesn't match the one defined in the JWT's Header."));
    }

    @Test
    public void shouldValidateIssuer() {
        String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withIssuer("auth0")
                .build()
                .verify(token);
        assertThat(jwt, is(notNullValue()));

        //  "iss": ["auth0", "okta"]
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, ()-> {
            String token1 = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withIssuer((String[]) null)
                    .build()
                    .verify(token1);
        });

        assertThat(e.getClaimName(), is("iss"));
    }

    @Test
    public void shouldValidateMultipleIssuers() {
        String auth0Token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M";
        String otherIssuertoken = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJvdGhlcklzc3VlciJ9.k4BCOJJl-c0_Y-49VD_mtt-u0QABKSV5i3W-RKc74co";
        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withIssuer("otherIssuer", "auth0")
                .build();

        assertThat(verifier.verify(auth0Token), is(notNullValue()));
        assertThat(verifier.verify(otherIssuertoken), is(notNullValue()));
    }

    @Test
    public void shouldThrowOnInvalidIssuer() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsImN0eSI6IkpXVCJ9.eyJpc3MiOiJhdXRoMCJ9.mZ0m_N1J4PgeqWmi903JuUoDRZDBPB7HwkS4nVyWH1M";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withIssuer("invalid")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer."));
        assertThat(e.getClaimName(), is(RegisteredClaims.ISSUER));
        assertThat(e.getClaimValue().asString(), is("auth0"));
    }

    @Test
    public void shouldThrowOnNullIssuer() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withIssuer("auth0")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'iss' value doesn't match the required issuer."));
        assertThat(e.getClaimName(), is(RegisteredClaims.ISSUER));
        assertThat(e.getClaimValue().isNull(), is(true));
    }

    @Test
    public void shouldThrowOnMissingIssuer() {
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String jwt = JWTCreator.init()
                    .sign(Algorithm.HMAC256("secret"));

            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withIssuer("nope")
                    .build()
                    .verify(jwt);
        });
        assertThat(e.getMessage(), is("The Claim 'iss' is not present in the JWT."));
        assertThat(e.getClaimName(), is("iss"));
    }

    @Test
    public void shouldValidateSubject() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withSubject("1234567890")
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowOnInvalidSubject() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withSubject("invalid")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'sub' value doesn't match the required one."));
        assertThat(e.getClaimName(), is(RegisteredClaims.SUBJECT));
        assertThat(e.getClaimValue().asString(), is("1234567890"));
    }

    @Test
    public void shouldAcceptAudienceWhenWithAudienceContainsAll() {
        // Token 'aud': ["Mark"]
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJNYXJrIn0.xWB6czYI0XObbVhLAxe55TwChWZg7zO08RxONWU2iY4";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withAudience("Mark")
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));

        // Token 'aud': ["Mark", "David"]
        String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIl19.6WfbIt8m61f9WlCYIQn5CThvw4UNyC66qrPaoinfssw";
        DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withAudience("Mark", "David")
                .build()
                .verify(tokenArr);

        assertThat(jwtArr, is(notNullValue()));
    }

    @Test
    public void shouldAllowWithAnyOfAudienceVerificationToOverrideWithAudience() {
        // Token 'aud' = ["Mark", "David", "John"]
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw";
        Verification verification = JWTVerifier.init(Algorithm.HMAC256("secret")).withAudience("Mark", "Jim");

        Exception exception = null;
        try {
            verification.build().verify(token);
        } catch (Exception e) {
            exception = e;

        }

        assertThat(exception, is(notNullValue()));
        assertThat(exception, is(instanceOf(IncorrectClaimException.class)));
        assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));

        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")).withAnyOfAudience("Mark", "Jim").build().verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldAllowWithAudienceVerificationToOverrideWithAnyOfAudience() {
        // Token 'aud' = ["Mark", "David", "John"]
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw";
        Verification verification = JWTVerifier.init(Algorithm.HMAC256("secret")).withAnyOfAudience("Jim");

        Exception exception = null;
        try {
            verification.build().verify(token);
        } catch (Exception e) {
            exception = e;

        }

        assertThat(exception, is(notNullValue()));
        assertThat(exception, is(instanceOf(IncorrectClaimException.class)));
        assertThat(exception.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));

        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret")).withAudience("Mark").build().verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldAcceptAudienceWhenWithAudienceAndPartialExpected() {
        // Token 'aud' = ["Mark", "David", "John"]
        String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw";
        DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withAudience("John")
                .build()
                .verify(tokenArr);

        assertThat(jwtArr, is(notNullValue()));
    }

    @Test
    public void shouldAcceptAudienceWhenAnyOfAudienceAndAllContained() {
        // Token 'aud' = ["Mark", "David", "John"]
        String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw";
        DecodedJWT jwtArr = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withAnyOfAudience("Mark", "David", "John")
                .build()
                .verify(tokenArr);

        assertThat(jwtArr, is(notNullValue()));
    }

    @Test
    public void shouldThrowWhenAudienceHasNoneOfExpectedAnyOfAudience() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            // Token 'aud' = ["Mark", "David", "John"]
            String tokenArr = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withAnyOfAudience("Joe", "Jim")
                    .build()
                    .verify(tokenArr);
        });
        assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));
        assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"}));
    }

    @Test
    public void shouldThrowWhenAudienceClaimDoesNotContainAllExpected() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            // Token 'aud' = ["Mark", "David", "John"]
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiTWFyayIsIkRhdmlkIiwiSm9obiJdfQ.DX5xXiCaYvr54x_iL0LZsJhK7O6HhAdHeDYkgDeb0Rw";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withAudience("Mark", "Joe")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));
        assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"Mark","David","John"}));
    }

    @Test
    public void shouldThrowWhenAudienceClaimIsNull() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            // Token 'aud': null
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withAudience("nope")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));
        assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE));
        assertThat(e.getClaimValue().isNull(), is(true));
    }

    @Test
    public void shouldThrowWhenAudienceClaimIsMissing(){
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withAudience("nope")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'aud' is not present in the JWT."));
        assertThat(e.getClaimName(), is("aud"));
    }

    @Test
    public void shouldThrowWhenAudienceClaimIsNullWithAnAudience() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            // Token 'aud': [null]
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpbbnVsbF19.2cBf7FbkX52h8Vmjnl1DY1PYe_J_YP0KsyeoeYmuca8";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withAnyOfAudience("nope")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));
        assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {null}));
    }

    @Test
    public void shouldThrowWhenExpectedEmptyList() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            // Token 'aud': 'wide audience'
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ3aWRlIGF1ZGllbmNlIn0.c9anq03XepcuEKWEVsPk9cck0sIIfrT6hHbBsCar49o";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withAnyOfAudience(new String[0])
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'aud' value doesn't contain the required audience."));
        assertThat(e.getClaimName(), is(RegisteredClaims.AUDIENCE));
        assertThat(e.getClaimValue().asString(), is("wide audience"));
    }

    @Test
    public void shouldNotReplaceWhenMultipleChecksAreAdded() {
        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withAudience((String[]) null)
                .withAudience()
                .withAnyOfAudience((String[]) null)
                .withAnyOfAudience()
                .build();

        assertThat(verifier.expectedChecks.size(), is(7)); //3 extra mandatory checks exp, nbf, iat
    }

    @Test
    public void shouldThrowOnNullCustomClaimName() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("The Custom Claim's name can't be null.");
        JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim(null, "value");
    }

    @Test
    public void shouldThrowWhenExpectedArrayClaimIsMissing() {
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcnJheSI6WzEsMiwzXX0.wKNFBcMdwIpdF9rXRxvexrzSM6umgSFqRO1WZj992YM";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withArrayClaim("missing", 1, 2, 3)
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT."));
        assertThat(e.getClaimName(), is("missing"));
    }

    @Test
    public void shouldThrowWhenExpectedClaimIsMissing() {
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbSI6InRleHQifQ.aZ27Ze35VvTqxpaSIK5ZcnYHr4SrvANlUbDR8fw9qsQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("missing", "text")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT."));
        assertThat(e.getClaimName(), is("missing"));
    }

    @Test
    public void shouldThrowOnInvalidCustomClaimValueOfTypeString() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("name", "value")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("name"));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"}));
    }

    @Test
    public void shouldThrowOnInvalidCustomClaimValueOfTypeInteger() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("name", 123)
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("name"));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"}));
    }

    @Test
    public void shouldThrowOnInvalidCustomClaimValueOfTypeDouble() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("name", 23.45)
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("name"));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"}));
    }

    @Test
    public void shouldThrowOnInvalidCustomClaimValueOfTypeBoolean() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("name", true)
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("name"));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"}));
    }


    @Test
    public void shouldThrowOnInvalidCustomClaimValueOfTypeDate() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("name", new Date())
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("name"));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"}));
    }

    @Test
    public void shouldThrowOnInvalidCustomClaimValue() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjpbInNvbWV0aGluZyJdfQ.3ENLez6tU_fG0SVFrGmISltZPiXLSHaz_dyn-XFTEGQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("name", "check")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'name' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("name"));
        assertThat(e.getClaimValue().asArray(String.class), is(new String[] {"something"}));
    }

    @Test
    public void shouldValidateCustomClaimOfTypeString() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidmFsdWUifQ.Jki8pvw6KGbxpMinufrgo6RDL1cu7AtNMJYVh6t-_cE";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", "value")
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomClaimOfTypeInteger() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoxMjN9.XZAudnA7h3_Al5kJydzLjw6RzZC3Q6OvnLEYlhNW7HA";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", 123)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomClaimOfTypeLong() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjo5MjIzMzcyMDM2ODU0Nzc2MDB9.km-IwQ5IDnTZFmuJzhSgvjTzGkn_Z5X29g4nAuVC56I";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", 922337203685477600L)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomClaimOfTypeDouble() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoyMy40NX0.7pyX2OmEGaU9q15T8bGFqRm-d3RVTYnqmZNZtxMKSlA";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", 23.45)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomClaimOfTypeBoolean() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjp0cnVlfQ.FwQ8VfsZNRqBa9PXMinSIQplfLU4-rkCLfIlTLg_MV0";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", true)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomClaimOfTypeDate() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoxNDc4ODkxNTIxfQ.mhioumeok8fghQEhTKF3QtQAksSvZ_9wIhJmgZLhJ6c";
        Date date = new Date(1478891521123L);
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", date)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldNotRemoveCustomClaimOfTypeDateWhenNull() {
        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("name", new Date())
                .withClaim("name", (Date) null)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(5));
    }

    @Test
    public void shouldValidateCustomArrayClaimOfTypeString() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbInRleHQiLCIxMjMiLCJ0cnVlIl19.lxM8EcmK1uSZRAPd0HUhXGZJdauRmZmLjoeqz4J9yAA";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withArrayClaim("name", "text", "123", "true")
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomArrayClaimOfTypeInteger() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withArrayClaim("name", 1, 2, 3)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomArrayClaimOfTypeLong() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbNTAwMDAwMDAwMDAxLDUwMDAwMDAwMDAwMiw1MDAwMDAwMDAwMDNdfQ.vzV7S0gbV9ZAVxChuIt4XZuSVTxMH536rFmoHzxmayM";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withArrayClaim("name", 500000000001L, 500000000002L, 500000000003L)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsInteger() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSwyLDNdfQ.UEuMKRQYrzKAiPpPLhIVawWkKWA1zj0_GderrWUIyFE";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withArrayClaim("name", 1L, 2L, 3L)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateCustomArrayClaimOfTypeLongWhenValueIsIntegerAndLong() {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpbMSw1MDAwMDAwMDAwMDIsNTAwMDAwMDAwMDAzXX0.PQjb2rPPpYjM2sItZEzZcjS2YbfPCp6xksTSPjpjTQA";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withArrayClaim("name", 1L, 500000000002L, 500000000003L)
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    // Generic Delta
    @Test
    public void shouldAddDefaultLeewayToDateClaims() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm);
        JWTVerifier verifier = verification
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(0L));
        assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(0L));
        assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(0L));
    }

    @Test
    public void shouldAddCustomLeewayToDateClaims() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm);
        JWTVerifier verifier = verification
                .acceptLeeway(1234L)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L));
        assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L));
        assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L));
    }

    @Test
    public void shouldOverrideDefaultIssuedAtLeeway() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm);
        JWTVerifier verifier = verification
                .acceptLeeway(1234L)
                .acceptIssuedAt(9999L)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(9999L));
        assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L));
        assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L));
    }

    @Test
    public void shouldOverrideDefaultExpiresAtLeeway() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm);
        JWTVerifier verifier = verification
                .acceptLeeway(1234L)
                .acceptExpiresAt(9999L)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L));
        assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(9999L));
        assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(1234L));
    }

    @Test
    public void shouldOverrideDefaultNotBeforeLeeway() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(algorithm);
        JWTVerifier verifier = verification
                .acceptLeeway(1234L)
                .acceptNotBefore(9999L)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verification.getLeewayFor(RegisteredClaims.ISSUED_AT), is(1234L));
        assertThat(verification.getLeewayFor(RegisteredClaims.EXPIRES_AT), is(1234L));
        assertThat(verification.getLeewayFor(RegisteredClaims.NOT_BEFORE), is(9999L));
    }

    @Test
    public void shouldThrowOnNegativeCustomLeeway() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Leeway value can't be negative.");
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.init(algorithm)
                .acceptLeeway(-1);
    }

    // Expires At

    @Test
    public void shouldValidateExpiresAtWithLeeway() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"))
                .acceptExpiresAt(2);
        DecodedJWT jwt = verification
                .build(mockOneSecondLater)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateExpiresAtIfPresent() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
        DecodedJWT jwt = verification
                .build(mockOneSecondEarlier)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowWhenExpiresAtIsNow() {
        // exp must be > now
        TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo";
            JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
            verification
                    .build(mockNow)
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z."));
        assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L)));
    }

    @Test
    public void shouldThrowOnInvalidExpiresAtIfPresent() {
        TokenExpiredException e = assertThrows(null, TokenExpiredException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo";
            JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
            verification
                    .build(mockOneSecondLater)
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Token has expired on 1970-01-18T02:26:32Z."));
        assertThat(e.getExpiredOn(), is(Instant.ofEpochSecond(1477592L)));
    }

    @Test
    public void shouldThrowOnNegativeExpiresAtLeeway() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Leeway value can't be negative.");
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.init(algorithm)
                .acceptExpiresAt(-1);
    }

    // Not before
    @Test
    public void shouldValidateNotBeforeWithLeeway() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"))
                .acceptNotBefore(2);
        DecodedJWT jwt = verification
                .build(mockOneSecondEarlier)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowOnInvalidNotBeforeIfPresent() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0Nzc1OTJ9.wq4ZmnSF2VOxcQBxPLfeh1J2Ozy1Tj5iUaERm3FKaw8";
            JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
            verification
                    .build(mockOneSecondEarlier)
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z."));
        assertThat(e.getClaimName(), is(RegisteredClaims.NOT_BEFORE));
        assertThat(e.getClaimValue().asLong(), is(1477592L));
    }

    @Test
    public void shouldValidateNotBeforeIfPresent() {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTN9.f4zVV0TbbTG5xxDjSoGZ320JIMchGoQCWrnT5MyQdT0";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
        DecodedJWT jwt = verification
                .build(mockOneSecondLater)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldAcceptNotBeforeEqualToNow() {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE0Nzc1OTJ9.71XBtRmkAa4iKnyhbS4NPW-Xr26eAVAdHZgmupS7a5o";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
        DecodedJWT jwt = verification
                .build(mockNow)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowOnNegativeNotBeforeLeeway() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Leeway value can't be negative.");
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.init(algorithm)
                .acceptNotBefore(-1);
    }

    // Issued At with future date
    @Test
    public void shouldThrowOnFutureIssuedAt() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE";
            JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));

            DecodedJWT jwt = verification.build(mockOneSecondEarlier).verify(token);
            assertThat(jwt, is(notNullValue()));
        });
        assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z."));
        assertThat(e.getClaimName(), is(RegisteredClaims.ISSUED_AT));
        assertThat(e.getClaimValue().asLong(), is(1477592L));
    }

    // Issued At with future date and ignore flag
    @Test
    public void shouldSkipIssuedAtVerificationWhenFlagIsPassed() {
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0Nzc1OTJ9.CWq-6pUXl1bFg81vqOUZbZrheO2kUBd2Xr3FUZmvudE";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
        verification.ignoreIssuedAt();

        DecodedJWT jwt = verification.build(mockOneSecondEarlier).verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowOnInvalidIssuedAtIfPresent() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo";
            JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
            verification
                    .build(mockOneSecondEarlier)
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Token can't be used before 1970-01-18T02:26:32Z."));
        assertThat(e.getClaimName(), is(RegisteredClaims.ISSUED_AT));
        assertThat(e.getClaimValue().asLong(), is(1477592L));
    }

    @Test
    public void shouldOverrideAcceptIssuedAtWhenIgnoreIssuedAtFlagPassedAndSkipTheVerification() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"))
                .acceptIssuedAt(1)
                .ignoreIssuedAt();
        DecodedJWT jwt = verification
                .build(mockOneSecondEarlier)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldValidateIssuedAtIfPresent() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Nzc1OTJ9.0WJky9eLN7kuxLyZlmbcXRL3Wy8hLoNCEk5CCl2M4lo";
        JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWTVerifier.init(Algorithm.HMAC256("secret"));
        DecodedJWT jwt = verification
                .build(mockNow)
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowOnNegativeIssuedAtLeeway() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Leeway value can't be negative.");
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier.init(algorithm)
                .acceptIssuedAt(-1);
    }

    @Test
    public void shouldValidateJWTId() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.0kegfXUvwOYioP8PDaLMY1IlV8HOAzSVz3EGL7-jWF4";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withJWTId("jwt_id_123")
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowOnInvalidJWTId() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqd3RfaWRfMTIzIn0.0kegfXUvwOYioP8PDaLMY1IlV8HOAzSVz3EGL7-jWF4";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withJWTId("invalid")
                    .build()
                    .verify(token);
        });
        assertThat(e.getMessage(), is("The Claim 'jti' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("jti"));
        assertThat(e.getClaimValue().asString(), is("jwt_id_123"));
    }

    @Test
    public void shouldNotRemoveClaimWhenPassingNull() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier verifier = JWTVerifier.init(algorithm)
                .withIssuer("iss")
                .withIssuer((String) null)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(5));

        verifier = JWTVerifier.init(algorithm)
                .withIssuer("iss")
                .withIssuer((String[]) null)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(5));
    }

    @Test
    public void shouldNotRemoveIssuerWhenPassingNullReference() {
        Algorithm algorithm = mock(Algorithm.class);
        JWTVerifier verifier = JWTVerifier.init(algorithm)
                .withIssuer((String) null)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(4));

        verifier = JWTVerifier.init(algorithm)
                .withIssuer((String[]) null)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(4));

        verifier = JWTVerifier.init(algorithm)
                .withIssuer()
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(4));

        String emptyIss = "  ";
        verifier = JWTVerifier.init(algorithm)
                .withIssuer(emptyIss)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
    }

    @Test
    public void shouldSkipClaimValidationsIfNoClaimsRequired() {
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .build()
                .verify(token);

        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldThrowWhenVerifyingClaimPresenceButClaimNotPresent() {
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String jwt = JWTCreator.init()
                    .withClaim("custom", "")
                    .sign(Algorithm.HMAC256("secret"));

            JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaimPresence("missing")
                    .build();

            verifier.verify(jwt);
        });
        assertThat(e.getMessage(), is("The Claim 'missing' is not present in the JWT."));
        assertThat(e.getClaimName(), is("missing"));
    }

    @Test
    public void shouldThrowWhenVerifyingClaimPresenceWhenClaimNameIsNull() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("The Custom Claim's name can't be null.");

        JWTCreator.init()
                .withClaim("custom", "value")
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence(null);
    }

    @Test
    public void shouldVerifyStringClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", "")
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyBooleanClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", true)
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyIntegerClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", 123)
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyLongClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", 922337203685477600L)
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyDoubleClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", 12.34)
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyListClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", Collections.singletonList("item"))
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyMapClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("custom", Collections.singletonMap("key", "value"))
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("custom")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldVerifyStandardClaimPresence() {
        String jwt = JWTCreator.init()
                .withClaim("aud", "any value")
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaimPresence("aud")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldSuccessfullyVerifyClaimWithPredicate() {
        String jwt = JWTCreator.init()
                .withClaim("claimName", "claimValue")
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("claimName", (claim, decodedJWT) -> "claimValue".equals(claim.asString()))
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldThrowWhenPredicateReturnsFalse() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String jwt = JWTCreator.init()
                    .withClaim("claimName", "claimValue")
                    .sign(Algorithm.HMAC256("secret"));

            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString()))
                    .build()
                    .verify(jwt);
        });
        assertThat(e.getMessage(), is("The Claim 'claimName' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("claimName"));
        assertThat(e.getClaimValue().asString(), is("claimValue"));
    }

    @Test
    public void shouldNotRemovePredicateCheckForNull() {
        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("claimName", (claim, decodedJWT) -> "nope".equals(claim.asString()))
                .withClaim("claimName", (BiPredicate<Claim, DecodedJWT>) null)
                .build();

        assertThat(verifier.expectedChecks, is(notNullValue()));
        assertThat(verifier.expectedChecks.size(), is(5));
    }

    @Test
    public void shouldSuccessfullyVerifyClaimWithNull() {
        String jwt = JWTCreator.init()
                .withNullClaim("claimName")
                .sign(Algorithm.HMAC256("secret"));

        JWTVerifier verifier = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withNullClaim("claimName")
                .build();

        DecodedJWT decodedJWT = verifier.verify(jwt);
        assertThat(decodedJWT, is(notNullValue()));
    }

    @Test
    public void shouldThrowWhenNullClaimHasValue() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String jwt = JWTCreator.init()
                    .withClaim("claimName", "value")
                    .sign(Algorithm.HMAC256("secret"));

            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withNullClaim("claimName")
                    .build()
                    .verify(jwt);
        });
        assertThat(e.getMessage(), is("The Claim 'claimName' value doesn't match the required one."));
        assertThat(e.getClaimName(), is("claimName"));
        assertThat(e.getClaimValue().asString(), is("value"));
    }

    @Test
    public void shouldThrowWhenNullClaimIsMissing() {
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String jwt = JWTCreator.init()
                    .withClaim("claimName", "value")
                    .sign(Algorithm.HMAC256("secret"));

            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withNullClaim("anotherClaimName")
                    .build()
                    .verify(jwt);
        });
        assertThat(e.getMessage(), is("The Claim 'anotherClaimName' is not present in the JWT."));
        assertThat(e.getClaimName(), is("anotherClaimName"));
    }

    @Test
    public void shouldCheckForNullValuesForSubject() {
        // sub = null
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGx9.y5brmQQ05OYwVvlTg83njUrz6tfpdyWNh17LHU6DxmI";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withSubject(null)
                .build()
                .verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldCheckForNullValuesInIssuer() {
        // iss = null
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOm51bGx9.OoiCLipSfflWxkFX2rytvtwEiJ8eAL0opkdXY_ap0qA";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withIssuer((String) null)
                .withIssuer((String[]) null)
                .withIssuer()
                .build()
                .verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldCheckForNullValuesInJwtId() {
        // jti = null
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOm51bGx9.z_MDyl8uPGH0q0jeB54wbYt3bwKXamU_3MO8LofGvZs";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withJWTId(null)
                .build()
                .verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldCheckForNullValuesInCustomClaims() {
        // jti = null
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOm51bGx9.inAuN3Q9UZ6WgbB63O43B1ero2MTqnfzzumr_5qYIls";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withClaim("custom", (Boolean) null)
                .withClaim("custom", (Integer) null)
                .withClaim("custom", (Long) null)
                .withClaim("custom", (Double) null)
                .withClaim("custom", (String) null)
                .withClaim("custom", (Date) null)
                .withClaim("custom", (Instant) null)
                .withClaim("custom", (BiPredicate<Claim, DecodedJWT>) null)
                .withArrayClaim("custom", (String[]) null)
                .withArrayClaim("custom", (Integer[]) null)
                .withArrayClaim("custom", (Long[]) null)
                .build()
                .verify(token);
        assertThat(jwt, is(notNullValue()));
    }


    @Test
    public void shouldCheckForNullValuesForAudience() {
        // aud = null
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI";
        DecodedJWT jwt = JWTVerifier.init(Algorithm.HMAC256("secret"))
                .withAudience((String[]) null)
                .withAudience((String) null)
                .withAudience()
                .withAnyOfAudience((String[]) null)
                .withAnyOfAudience((String) null)
                .withAnyOfAudience()
                .build()
                .verify(token);
        assertThat(jwt, is(notNullValue()));
    }

    @Test
    public void shouldCheckForClaimPresenceEvenForNormalClaimChecks() {
        MissingClaimException e = assertThrows(null, MissingClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjpudWxsfQ.bpPyquk3b8KepErKgTidjJ1ZwiOGuoTxam2_x7cElKI";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("custom", true)
                    .build()
                    .verify(token);
        });
        assertThat(e.getClaimName(), is("custom"));
    }

    @Test
    public void shouldCheckForWrongLongClaim() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOjF9.00btiK0sv8pQ2T-hOr9GC5x2osi7--Bsk4pS5cTikqQ";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withClaim("custom", 2L)
                    .build()
                    .verify(token);
        });
        assertThat(e.getClaimName(), is("custom"));
        assertThat(e.getClaimValue().asLong(), is(1L));
    }

    @Test
    public void shouldCheckForWrongLongArrayClaim() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withArrayClaim("custom", 2L)
                    .build()
                    .verify(token);
        });
        assertThat(e.getClaimName(), is("custom"));
    }

    @Test
    public void shouldCheckForWrongStringArrayClaim() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withArrayClaim("custom", "2L")
                    .build()
                    .verify(token);
        });
        assertThat(e.getClaimName(), is("custom"));
    }

    @Test
    public void shouldCheckForWrongIntegerArrayClaim() {
        IncorrectClaimException e = assertThrows(null, IncorrectClaimException.class, () -> {
            String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjdXN0b20iOlsxXX0.R9ZSmgtJng062rcEc59u4VKCq89Yk5VlkN9BuMTMvr0";
            JWTVerifier.init(Algorithm.HMAC256("secret"))
                    .withArrayClaim("custom", 2)
                    .build()
                    .verify(token);
        });
        assertThat(e.getClaimName(), is("custom"));
    }
}