@Override
  public Token redeem(@NonNull String code, String redirectUri, String clientId) {

    var redeemed = codeRepo.remove(code).orElse(null);
    if (redeemed == null) {
      return null;
    }

    if (!validateCode(redeemed, redirectUri, clientId)) {
      return null;
    }

    var accessTokenTtl = Duration.ofMinutes(5);
    return new Token(
        issueAccessToken(accessTokenTtl, redeemed.clientId()),
        issueIdToken(redeemed.clientId(), redeemed.nonce(), redeemed.federatedIdToken()),
        accessTokenTtl.getSeconds());
  }
@Test
  void redeem_idToken() throws JOSEException, ParseException {
    var issuer = URI.create("https://idp.example.com");

    var k = genKey();
    var keyStore = mock(KeyStore.class);
    when(keyStore.signingKey()).thenReturn(k);
    var codeRepo = mock(CodeRepo.class);

    var sut = new TokenIssuerImpl(issuer, keyStore, codeRepo);

    var id = UUID.randomUUID().toString();

    var nonce = UUID.randomUUID().toString();
    var redirectUri = URI.create("https://myapp.example.com/callback");
    var clientId = "myapp";

    var federatedIdToken =
        new IdTokenJWS(
            null,
            new IdToken(
                null, "tobias", null, 0, 0, 0, null, null, null, null, null, null, null, null, null,
                null, null, null, null));

    var code =
        new Code(
            id,
            null,
            Instant.now().plusSeconds(10),
            redirectUri,
            nonce,
            clientId,
            federatedIdToken);

    when(codeRepo.remove(id)).thenReturn(Optional.of(code));

    // when
    var token = sut.redeem(id, redirectUri.toString(), clientId);

    // then
    var idToken = token.idToken();

    var jws = JWSObject.parse(idToken);
    var verifier = new ECDSAVerifier(k);
    jws.verify(verifier);

    assertIdTokenClaims(jws, nonce, issuer, clientId);
  }