Create private key and certificate

  1. openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
  2. openssl req -new -key private_key.pem -out request.csr
  3. openssl x509 -req -days 365 -in request.csr -signkey private_key.pem -out certificate.pem
After this, the request.csr can be deleted. We now have the private key for signing a request JWT, and the certificate we need to register in EntraID and use for creating a x5t thumbprint. Requesting a access token with certificate is done by creating another request JWT locally, sign it with a private key whos public certificate was registered on an application in EntraID. In Java, we do it like so: In this example, we use library:
    
      <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>4.4.0</version>
      </dependency>
    

    //Read the X.509 certificate
    final CertificateFactory cf = CertificateFactory.getInstance("X.509");
    final X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStreamFromCertificatePem);
    
    //Read the private key for signing the JWT
    final String privateKeyString = privateKeyStringFromPem
                    .replace("-----BEGIN PRIVATE KEY-----", "")
                    .replaceAll(System.lineSeparator(), "")
                    .replace("-----END PRIVATE KEY-----", "");
    final byte[] encodedPrivateKey = Base64.getDecoder().decode(privateKeyString);
    final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
    final RSAPrivateKey privateKey (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    
    //Calculate the x5t thumbprint from the certificate
    final byte[] derEncodedCert = certificate.getEncoded();
    final MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
    final byte[] certHash = sha1.digest(derEncodedCert);
    final String x5tThumbPrint = Base64.getUrlEncoder().withoutPadding().encodeToString(certHash);
    
    //Create the algorithm used by the JWT signing process
    final Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) certificate.getPublicKey(), privateKey);
    
    //Create the request JWT
    final String requestJwt = JWT.create()
                    .withJWTId(UUID.randomUUID().toString())
                    .withHeader(Map.of("x5t", x5tThumbPrint))
                    .withAudience(audience)
                    .withExpiresAt(Instant.now().plusSeconds(tokenLifetimeInSeconds))
                    .withNotBefore(Instant.now())
                    .withSubject(clientId) //Client id
                    .withIssuer(clientId) //Client id
                    .withIssuedAt(Instant.now())
                    .sign(algorithm);
    
    //Call the authentication endpoint
    //E.g. https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
    //Using Spring RestTemplate
    final HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    final MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("scope", scope);
    map.add("client_id", clientId);
    map.add("grant_type", "client_credentials");
    map.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
    map.add("client_assertion", requestJwt);
    final HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(map, headers);
    final ResponseEntity<JsonNode> restResponse = restTemplate.exchange(oauth2LogonUrl, HttpMethod.POST, httpEntity, JsonNode.class, tenantId);
    //Normal response JSON from auth server. Get access_token etc from it.