String issueIdToken(String audience, String nonce, IdTokenJWS federatedIdToken) {
    try {
      var jwk = keyStore.signingKey();
      var signer = new ECDSASigner(jwk);

      // Prepare JWT with claims set
      var now = clock.instant();
      var claimsBuilder =
          new JWTClaimsSet.Builder()
              .issuer(issuer.toString())
              .audience(audience)
              .subject(deriveFederatedSubject(federatedIdToken.body()))
              .issueTime(Date.from(now))
              .expirationTime(Date.from(now.plus(Duration.ofHours(8))));

      if (nonce != null) {
        claimsBuilder.claim("nonce", nonce);
      }

      // complete list of scopes and corresponding claims:
      // https://fachportal.gematik.de/fachportal-import/files/gemSpec_IDP_Sek_V2.0.1.pdf
      // Specification 4.2.4  - A_22989 -

      claimsBuilder.claim("birthdate", federatedIdToken.body().telematikBirthdate());
      claimsBuilder.claim("urn:telematik:claims:alter", federatedIdToken.body().telematikAge());
      claimsBuilder.claim(
          "urn:telematik:claims:display_name", federatedIdToken.body().telematikDisplayName());
      claimsBuilder.claim(
          "urn:telematik:claims:given_name", federatedIdToken.body().telematikGivenName());
      claimsBuilder.claim(
          "urn:telematik:claims:geschlecht", federatedIdToken.body().telematikGender());
      claimsBuilder.claim("urn:telematik:claims:email", federatedIdToken.body().telematikEmail());
      claimsBuilder.claim(
          "urn:telematik:claims:profession", federatedIdToken.body().telematikProfession());
      claimsBuilder.claim("urn:telematik:claims:id", federatedIdToken.body().telematikKvnr());
      claimsBuilder.claim(
          "urn:telematik:claims:organization", federatedIdToken.body().telematikOrganization());

      var claims = claimsBuilder.build();

      var signedJWT =
          new SignedJWT(
              new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(jwk.getKeyID()).build(), claims);

      signedJWT.sign(signer);

      return signedJWT.serialize();
    } catch (JOSEException e) {
      throw new RuntimeException(e);
    }
  }
@Test
  void issueIdToken_mapsAllDefinedClaims() 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 federatedSub = "S12349345";
    var telematikBirthdate = "2020-01-01";
    var telematikAlter = "12";
    var telematikDisplayName = "Thilo";
    var telematikGivenName = "Tester";
    var telematikGeschlecht = "d";
    var telematikEmail = "t.tester@example.com";
    var telematikProfession = "1.12.234.234.1234.234";
    var telematikKvnr = "X1234";
    var telematikOrganization = "telematikOrg";

    var federatedIdToken =
        new IdTokenJWS(
            null,
            new IdToken(
                null,
                federatedSub,
                null,
                0,
                0,
                0,
                null,
                null,
                null,
                "toplevel+email@example.com",
                telematikBirthdate,
                telematikAlter,
                telematikDisplayName,
                telematikGivenName,
                telematikGeschlecht,
                telematikEmail,
                telematikProfession,
                telematikKvnr,
                telematikOrganization));

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

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

    var idToken = sut.issueIdToken(clientId, nonce, federatedIdToken);

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

    var body = jws.getPayload().toJSONObject();

    assertEquals(telematikBirthdate, body.get("birthdate"));
    assertEquals(telematikAlter, body.get("urn:telematik:claims:alter"));
    assertEquals(telematikDisplayName, body.get("urn:telematik:claims:display_name"));
    assertEquals(telematikGivenName, body.get("urn:telematik:claims:given_name"));
    assertEquals(telematikGeschlecht, body.get("urn:telematik:claims:geschlecht"));
    assertEquals(telematikEmail, body.get("urn:telematik:claims:email"));
    assertEquals(telematikProfession, body.get("urn:telematik:claims:profession"));
    assertEquals(telematikKvnr, body.get("urn:telematik:claims:id"));
    assertEquals(telematikOrganization, body.get("urn:telematik:claims:organization"));
  }