17

I'm building an application that needs access to our clients' Office 365 Management Activities. I've followed the steps outlined in this Azure Active Directory overview, and am able to use the OAuth code to acquire an initial Access Token, as well as use this token to set up O365 subscriptions.

However, when I use the refresh_token provided with my initial token to acquire a new Access Token, I get the following error:

{"error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID '8f72f805-dfd2-428d-8b0e-771a98d26c16'. Send an interactive authorization request for this user and resource.\r\nTrace ID: df229c3f-8f28-420b-9ac3-321ab1b2ad09\r\nCorrelation ID: 0e0f2bcb-4b19-458a-8556-2a6d4e51379f\r\nTimestamp: 2016-10-03 17:33:20Z","error":"invalid_grant"}

Since I'm able to acquire and use the initial Access Token, I'm pretty sure that the user is granting my applications some permissions. Is there a specific permission that I need in order to acquire a new Access Token using the Refresh Token?

Edit: Specifically, I'm using the com.microsoft.azure::adal4j java package, AuthenticationContext class, acquireTokenByAuthorizationCode and acquireTokenByRefreshToken methods:

public class AzureProvisioner {
    private final AuthenticationContext authService = new AuthenticationContext(
            "https://login.windows.net/common/oauth2/token", true, Executors.newSingleThreadExecutor());
    private final ClientCredential clientCredential = new ClientCredential("azureAppId", "azureAppSecret");
    public static final String resource = "https://manage.office.com";
    // Internal implementation of REST interface; Microsoft didn't provide a Java Library
    final Office365ManagementApi managementApi;

    public void acquireToken(final String authCode, final URI redirectUri) {
        final AuthenticationResult authResult = authService.acquireTokenByAuthorizationCode(
                authCode, redirectUri, clientCredential, resource, null).get()
        // internal library code, gets the "tid" field from parsing the JWT token
        final String tenantId = JwtAccessToken.fromToken(authResult.getAccessToken()).getTid();

        // works
        createInitialSubscription(customerId, authResult.getAccessToken(), tenantId);

        // throws an error
        final AuthenticationResult refreshResult = authService.acquireTokenByRefreshToken(
                authResult.getRefreshToken(), clientCredential, null).get();
    }

    private void createInitialSubscription(final String accessToken, final String tenantId) {
        final String authHeader = "Authorization: Bearer " + accessToken;
        final String contentType = "Audit.AzureActiveDirectory";
        // internal implementation
        final CreateWebhookRequest requestBody = new CreateWebhookRequest();
        managementApi.createSubscription(authHeader, tenantId, contentType, requestBody);
    }
}

The same code, without any external dependencies, also does not work for me:

public class AzureProvisioner {
    private final AuthenticationContext authService = new AuthenticationContext(
            "https://login.windows.net/common/oauth2/token", true, Executors.newSingleThreadExecutor());
    private final ClientCredential clientCredential = new ClientCredential("8f72f805-dfd2-428d-8b0e-771a98d26c16", "secret");
    public final String resource = "https://manage.office.com";
    private URI redirectUri = new URI("https://localhost");

    private static final String oAuthUrl = "https://login.windows.net/common/oauth2/authorize?response_type=code&client_id=8f72f805-dfd2-428d-8b0e-771a98d26c16&resource=https%3A%2F%2Fmanage.office.com&redirect_uri=https%3A%2F%2Flocalhost";

    public AzureProvisioner() throws Exception {
        // do nothing
    }

    public static void main(String... args) throws Exception {
        final String authCode = "AQABAAAAAADRNYRQ3dhRSrm...";
        new AzureProvisioner().acquireToken(authCode);
    }

    public void acquireToken(final String authCode) throws Exception {
        final AuthenticationResult authResult = authService.acquireTokenByAuthorizationCode(
                authCode, redirectUri, clientCredential, resource, null).get();
        System.out.println(authResult.getAccessToken());

        // throws an error
        final AuthenticationResult refreshResult = authService.acquireTokenByRefreshToken(
                authResult.getRefreshToken(), clientCredential, resource, null).get();
        System.out.println(refreshResult.getAccessToken());
    }
}

Using a proxy, I took a trace of the https refresh request:

Method: POST
Protocol-Version: HTTP/1.1
Protocol: https
Host: login.windows.net
File: /common/oauth2/token
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 876

refresh_token={token}
&resource=https%3A%2F%2Fmanage.office.com
&grant_type=refresh_token
&scope=openid
&client_secret={secret}
&client_id=8f72f805-dfd2-428d-8b0e-771a98d26c16
Andrew Rueckert
  • 4,858
  • 1
  • 33
  • 44
  • Can you show how you're acquiring the initial access token and refresh token, and how you're attempting to use the refresh token? – Philippe Signoret Sep 25 '16 at 16:12
  • Updated the question with a code sample. – Andrew Rueckert Sep 27 '16 at 18:54
  • I'm able to run what you have above (commenting out lines with references to `CreateWebhookRequest` and `Office365ManagementApi`) with no errors. Can you add a version of the code that still reproduces the issue, but doesn't have any dependencies on anything except ADAL4J? – Philippe Signoret Sep 29 '16 at 12:37
  • Really? I've also locally tried a paired-down version that only gets a token and immediately attempts to refresh it. (Edited in above.) Does your Azure application have any Required Permissions that I may be lacking? I'm getting the OAuth code as a Global Administrator - might that be an issue? – Andrew Rueckert Sep 29 '16 at 18:10
  • Are you sure you tried that exact code? You have a few unhandled exceptions (when initializing `authService`, and in `acquireToken`), and there's a semi-colon missing. – Philippe Signoret Sep 30 '16 at 12:42
  • Can you also share how you are constructing the Authorization Request? (The one that results in a redirect back to your app, with the Authorization Code, which you use in `acquireTokenByAuthorizationCode`.) – Philippe Signoret Sep 30 '16 at 12:43
  • Pasted in the exact code I'm using, as well as the OAuth url. Once it redirects back to localhost, I'm copy/pasting the `code` url parameter into my java code. – Andrew Rueckert Sep 30 '16 at 18:25
  • A couple of side comments first. (a) please move to login.microsoftonline.com instead of login.windows.net. (b) please DO NOT decode the access token in your client like that. That access token is the property of https://manage.office.com, it's for its eyes only. At any point in time manage.office.com could choose to change that token format or encrypt it, and you app would break. Instead, the `authResult` should contain a `userInfo` object or similar you can use. – dstrockis Oct 03 '16 at 16:51
  • Now onto the real problem. It's strange to me that you would receive such an error at the `/token` endpoint but not at the `/authorize` endpoint. And your app seems to be requesting the right permissions. Can you please post the full error message, including the non-redacted error description, timestamp, and correlationId? – dstrockis Oct 03 '16 at 16:53
  • Posted the full error message, as well as a trace of the HTTPS request that my application is making. (I also changed the domain, and will look for another way of getting the user's tenantId.) – Andrew Rueckert Oct 03 '16 at 18:08
  • 1
    @dstrokis There's no `tid` or similar in [UserInfo](https://github.com/AzureAD/azure-activedirectory-library-for-java/blob/dev/src/main/java/com/microsoft/aad/adal4j/UserInfo.java). @AndrewRueckert you *can* look into the ID token, if you want (`authResult.getIdToken()`). – Philippe Signoret Oct 03 '16 at 20:27
  • I'm grasping at straws here, but can you try registering a new application? (Your code works for me.) – Philippe Signoret Oct 03 '16 at 20:46
  • I've tried both registering a new application and creating a new trial Office365 account. :( I'm thinking that there must be some configuration that I'm failing to apply to my account or application, but I can't figure out what would cause *this* particular error. – Andrew Rueckert Oct 03 '16 at 21:12
  • @AndrewRueckert What happens if you add `&prompt=admin_consent` to the authorization URL? (Expected: You get prompted for admin consent, you consent, everything else works as expected.) – Philippe Signoret Oct 04 '16 at 17:09
  • If I add `&prompt=admin_consent`, the authorization code comes back with `&admin_consent=True`, and I am able to use the code acquire an access_token; however, I still get the same error when I attempt to use the refresh_token. – Andrew Rueckert Oct 04 '16 at 18:04

2 Answers2

2

It turns out that the root issue was with my application permissions. Under My Application > Settings > Required Permissions > Office 365 Management APIs, I had selected the "Application Permissions", where I needed to select the "Delegated Permissions". Swapping those over, my code immediately started working as expected.

wrong!

Andrew Rueckert
  • 4,858
  • 1
  • 33
  • 44
0

ADAL uses the stored refresh tokens automatically and transparently, you aren't required to perform any explicit action. AcquireTOkenByRefreshToken is in the ADAL surface for legacy reasons, and has been removed from version 3.x. More background at http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/

Mitin Dixit
  • 541
  • 4
  • 9
  • That appears to be a C# library for client applications. I am writing Java code for a multi-tenant server-based application. – Andrew Rueckert Sep 27 '16 at 23:04
  • point here was to explain that use of refresh token process is automatic and transparent independent of the language you use, but behind the scene you are using the AAD. If you create the new token that will not validate since there was the token that has not expired. – Mitin Dixit Sep 28 '16 at 12:42
  • 2
    @MitinDixit: No, this is not true. The token cache that is included in ADAL for .NET has not been implemented in ADAL for Java. – Philippe Signoret Sep 29 '16 at 11:21
  • @AndrewRueckert this issue looks similar http://stackoverflow.com/questions/34775287/oauth2-with-azure-ad-not-getting-user-consent – Brent Schmaltz Oct 03 '16 at 23:24
  • I looked at that, but unfortunately it's slightly different. That user was unable to get an initial token because they weren't logged in as an admin user. I am logged in as an admin user, and am able to get an initial token; however, I am unable to refresh it. – Andrew Rueckert Oct 03 '16 at 23:40
  • hi, were you able to find a solution to this ? – Imal Hasaranga Perera Oct 08 '16 at 14:45