2

I am following this guide to the client credentials flow and this guide to the JWT token needed. The ultimate objective is to access calendar information through the Microsoft Graph API. Calls to the Graph API need to be authenticated with an access token which is obtained from the Microsoft token endpoint.

But calling the token endpoint itself requires some form of authentication, which can be either by shared secret or by a certificate-signed JWT token. The shared secret approach is well-documented and appears to work fine, except that the Graph service I am trying to call rejects the access token as not secure enough - it seems a certificate-signed JWT token is required. I have been unable to find any Java examples of this second approach, and what I have implemented so far following the above guides doesn't work.

Using JJWT, the code for generating the token looks like:

PrivateKey key = loadPrivateKey();
    String jwt = Jwts.builder()
        .setHeaderParam("typ", "JWT")
        .setHeaderParam("alg", "RS256")
        .setHeaderParam("x5t", "A7...89")
        .setSubject(clientId)
        .setExpiration(new Date(System.currentTimeMillis() + 200000))
        .setIssuer(clientId)
        .setNotBefore(new Date())
        .setAudience("https://login.microsoftonline.com/" + tenantId + "/oauth2/token")
        .setId(UUID.randomUUID().toString())
        .signWith(
            SignatureAlgorithm.RS256,
            key)
        .compact();

loadPrivateKey() uses BouncyCastle classes:

KeyFactory factory = KeyFactory.getInstance("RSA");
PemObject pemObject;
PemReader pemReader = new PemReader(new StringReader(pemFileContent));
try {
    pemObject = pemReader.readPemObject();
} finally {
    pemReader.close();
}
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
return factory.generatePrivate(privKeySpec);

I created a key pair in the apps.dev.microsoft.com console, downloaded the private key and converted it to PEM format using openssl, then pasted the PEM content into the pemFileContent variable used above. openssl calculates the same thumbprint for the PEM version as the MS application console.

I use the JWT token in a call to a Retrofit service:

default Call<APIToken> getAccessToken(String tenantId, String clientId, String clientAssertion)
{
    return this.getAccessToken(
            tenantId,
            clientId,
            clientAssertion,
            "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
            "https://graph.microsoft.com/.default",
            "client_credentials");
}

@FormUrlEncoded
@Headers({
    "Host: login.microsoftonline.com",
    "Content-Type: application/x-www-form-urlencoded"
})
@POST("/{tenant_id}/oauth2/token")
Call<APIToken> getAccessToken(
        @Path("tenant_id") String tenantId,
        @Field("client_id") String clientId,
        @Field("client_assertion") String clientAssertion,
        @Field("client_assertion_type") String clientAssertionType,
        @Field("scope") String scope,
        @Field("grant_type") String grantType);

Everything looks good in the debugging output:

header={typ=JWT, alg=RS256, x5t=A7...89},body={sub=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, exp=1517656715, iss=b0a0fd13-1e86-4ef3-a003-c53eaf21daa4, nbf=1517656515, aud=https://login.microsoftonline.com/87ad3067-2703-49ea-8cd2-a094fc3ee413/oauth2/token, jti=4fa5db4f-76b7-4122-9c5a-092ac74fd0fa},signature=....342 characters....

But the response is 401 Unauthorized, with the error report:

{"error":"invalid_client","error_description":"AADSTS70002: Error validating credentials. AADSTS50012: Client assertion contains an invalid signature. [Reason - The key was not found., Thumbprint of key used by client: '03B....3D', Configured keys: [Key0:Start=02/03/2018, End=12/31/2099, Thumbprint=lQ...M4;]]\r\nTrace ID: b4775394-51cc-4a4a-b927-50bd05421100\r\nCorrelation ID: f9264339-9b85-4942-9404-af941aa0331c\r\nTimestamp: 2018-02-03 11:15:18Z","error_codes":[70002,50012],"timestamp":"2018-02-03 11:15:18Z","trace_id":"b4775394-51cc-4a4a-b927-50bd05421100","correlation_id":"f9264339-9b85-4942-9404-af941aa0331c"}

I am at a loss as to where to look for the problem. Neither of the thumbprint values mentioned in the error correspond to the thumbprint of the private key. Others have reported this error message when using the wrong signature algorithm, but RS256 is the documented algorithm to use. I'm looking for either a pointer to what I'm getting wrong, or a working example in Java (I'm not fussy about which libraries are used) of the client credentials flow using a signed JWT token (I have found examples using a shared secret, and they work OK, but the MS Graph API rejects the token as not being secure enough).

Jon Moore
  • 61
  • 3
  • Which endpoint on MS Graph rejects the token? – juunas Feb 04 '18 at 19:58
  • 1
    I was trying to use the streaming subscriptions API: /api/beta/me/subscriptions. Unfortunately I can't reproduce the exact error at the moment because the application management console (https://apps.dev.microsoft.com) has had a 'temporary problem' since yesterday when I try to create a new password. – Jon Moore Feb 06 '18 at 12:59
  • 1
    The error from the subscriptions API is "The access token is acquired using an authentication method that is too weak to allow access for this application. Presented auth strength was 1, required is 2.";error_category="invalid_token". i.e. the token acquired using a password is not secure enough to grant API access - I have to present a certificate-signed JWT instead of the password. – Jon Moore Feb 06 '18 at 17:43
  • see my answer to a similar issue. It's most likely the computed certificate fingerprint. I had to do it manually using openssl and use that for the x5t https://stackoverflow.com/questions/50657463/how-to-obtain-value-of-x5t-using-certificate-credentials-for-application-authe/52625165#52625165 – codebrane Oct 03 '18 at 10:37

0 Answers0