2

I have an Azure AD JWT token that is obtained using Msal library but when I try to validate this token something is wrong:

Client: A Sharepoint Web Part

const config = {
 auth: {
     clientId: "xxxxx",
     authority: "https://login.microsoftonline.com/yyyyyy"
 }
};

const myMSALObj = new UserAgentApplication(config);

let accessTokenRequest = {
 scopes: ["user.read"],
 loginHint: this.context.pageContext.user.loginName,
 extraQueryParameters: {domain_hint: 'organizations'}
}

myMSALObj.acquireTokenSilent(accessTokenRequest).then(
function(accessTokenResponse) { 
// Acquire token silent success 
let accessToken = accessTokenResponse.accessToken;

On the other hand I have a server app (Java) where the access token is validated

Validator:

<dependency>
  <groupId>com.microsoft.azure</groupId>
  <artifactId>azure-storage</artifactId>
  <version>8.6.2</version>
</dependency>

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>jwks-rsa</artifactId>
  <version>0.11.0</version>
</dependency>

Code

 String token="<your AD token>";
    DecodedJWT jwt = JWT.decode(token);
    System.out.println(jwt.getKeyId());

    JwkProvider provider = null;
    Jwk jwk =null;
    Algorithm algorithm=null;

    try {
       provider = new UrlJwkProvider(new URL("https://login.microsoftonline.com/common/discovery/keys"));
      jwk = provider.get(jwt.getKeyId());
      algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
      algorithm.verify(jwt);// if the token signature is invalid, the 
    method will throw SignatureVerificationException
     } catch (MalformedURLException e) {
         e.printStackTrace();
     } catch (JwkException e) {
        e.printStackTrace();
     }catch(SignatureVerificationException e){
       System.out.println(e.getMessage());
     }

My problem is that when I try to validate this token I obtained this error: The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA

I'm stuck with this, If the token is right, Why I have this error?

Regards

vcima
  • 421
  • 1
  • 7
  • 20
  • Two questions: 1. Are you using MSAL.js? 2. Are you manually validating the token after MSAL acquires it? MSAL should be validating it for you, so you shouldn't have to do any explicit validation yourself. – Toby Artisan Mar 31 '20 at 15:30
  • Hi @Toby, Thanks for your reply. Yes, I'm using MSAL.js to obtain the token in my client. After that I call an external API with this token. When I try to validate the token something goes wrong and fails. I have added to the code my validator – vcima Mar 31 '20 at 15:39
  • Are you able to get a copy of the JWT token and decode it at https://jwt.io/? If you can, then you can verify that the token's signature was signed with the RSA256 algorithm by looking at the HEADER area at jwt.io. If it's not using that algorithm, then that would be the problem. – Toby Artisan Mar 31 '20 at 15:50
  • If the token is signed using the RSA256 algorithm, then do you know if Azure AD is using the default settings for issuing a token, or did you set up a custom configuration with a custom signing algorithm and/or custom certificate? If you're using a custom certificate, then ensure the following about the certificate: 1. It's not expired. 2. It's issued by a public authority. If it's issued by a private authority, then you'll need custom code to trust that issuer. – Toby Artisan Mar 31 '20 at 15:52
  • Hi @Toby, yes "alg": "RS256", it's right? – vcima Mar 31 '20 at 15:55
  • In principle our Azure AD is with the default configuration – vcima Mar 31 '20 at 15:58
  • Yes, that looks correct, thank you. Another thing to try is that I noticed in your MSAL.js code you use an "authority" of "https://login.microsoftonline.com/yyyyyy". While the API code is using a UrlJwkProvider of "https://login.microsoftonline.com/common/discovery/keys". Can you try opening the URL of https://login.microsoftonline.com/yyyyyy/discovery/v2.0/keys to see if it is available? If it is available, then it might be using different keys than the other URL. And if it is available, then try using the URL with /yyyyyy in the API code. – Toby Artisan Mar 31 '20 at 16:00
  • Are you using Auth0's library (or a different library) in your API to validate the algorithm? – Toby Artisan Mar 31 '20 at 16:23
  • Are you using Auth0's library in your API to validate the algorithm? It looks like similar code to Auth0. If you are, then there is an alternative way to validate the token at https://github.com/auth0/java-jwt#verify-a-token. If you try that, then you'd want to use a different value for withIssuer since they use "auth0". If you're not using the Auth0 library, then you can disregard this comment. – Toby Artisan Mar 31 '20 at 16:41
  • Please do not validate tokens for Apis that do not belong to you. See [this](https://stackoverflow.com/questions/60959927/access-token-validation-fails-if-scope-if-graph-microsoft-com/60968783?noredirect=1#comment107865220_60968783) answer. – Kalyan Krishna Apr 01 '20 at 10:47
  • Ah, yes, I missed that the scope was for user.read, which is for the Microsoft Graph API instead of for their API. – Toby Artisan May 04 '20 at 14:26

2 Answers2

2

I noticed that the scope is user.read which means the token is for Microsoft Graph API.

Please note:

If you're a client getting a token for Graph, assume that it's an encrypted string that you should never look at - sometimes it will be. We use a special token format for Graph that they know how to validate - you shouldn't be looking at access tokens if they're not for you.

You can use this access token to call Microsoft Graph API directly, if the token is wrong, you will get the response from Microsoft API server.

Reference:

https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609#issuecomment-529537264

Tony Ju
  • 14,891
  • 3
  • 17
  • 31
  • Hi @Tony Ju, you are right. You can't validate this token with this method using user.read, because the result is a Graph Token. – vcima Apr 07 '20 at 07:41
2

Finally, It works with something like this.

  1. To obtain the token (using adal in the Web Part):

        // Obtaining token provider
        let tp = await this.context.aadTokenProviderFactory.getTokenProvider();
        let config = tp["_defaultConfiguration"];
        let aadInstanceUrl = config.aadInstanceUrl[length - 1] === "/" ? config.aadInstanceUrl : config.aadInstanceUrl + "/";
    
        // Config context
        let ctx = new AuthenticationContext({
            tenant: tenantId,
            clientId: clientId,
            instance: aadInstanceUrl,
            redirectUri: config.redirectUri,
            extraQueryParameter: "login_hint=" + encodeURIComponent(loginName),
            loadFrameTimeout: 60000
        });
    
        // Check user
        let cu = ctx.getCachedUser();
    
        console.log("USER", cu, loginName, ctx);
        if (cu && cu.userName.toLowerCase() !== loginName.toLowerCase()) {
            console.log("Clean user cache");
            ctx.clearCache();
        }
    
        // Login process
        console.log("Login process");
    
        // Obtaining Azure AD Token
        let azureADToken = this.acquireToken(ctx, clientId);
    
  2. To validate the token:

    String token = "XXXXXX";
    
    DecodedJWT jwt = JWT.decode(token);
    System.out.println(jwt.getKeyId());
    
    JwkProvider provider = null;
    Jwk jwk = null;
    Algorithm algorithm = null;
    
    try {
        provider = new UrlJwkProvider(new URL("https://login.microsoftonline.com/common/discovery/keys"));
        jwk = provider.get(jwt.getKeyId());
        algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
        algorithm.verify(jwt);// if the token signature is invalid, the method will throw
        // SignatureVerificationException
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (JwkException e) {
        e.printStackTrace();
    } catch (SignatureVerificationException e) {
    
        System.out.println(e.getMessage());
    
    }
    
    System.out.println("works!");
    

With this dependencies:

  <dependencies>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.1</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.1</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
            <version>0.11.1</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.9.10</version>
            <type>bundle</type>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
            <type>bundle</type>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.10</version>
            <type>bundle</type>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
        <!-- JUNIT -->
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-storage</artifactId>
            <version>8.6.2</version>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>jwks-rsa</artifactId>
            <version>0.11.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.2</version>
        </dependency>

    </dependencies>
vcima
  • 421
  • 1
  • 7
  • 20