3

I have a mobile app (Flutter) where I'm allowing the user to backup their data to their personal OneDrive, so if they lose their device they can restore data from their OneDrive. I'm using a delegated authentication workflow to access personal / consumer OneDrive using the Microsoft graph REST API, logging in using OAuth, with the scope Files.ReadWrite.All (and offline_access). The problem is that after uploading a file, I cannot then download the contents of the same file, even though it is the same user, the same scopes, the same device, the same app, the same login session. I can't understand why.

Here's the scenario:

  1. I'm using flutter_appauth to request the user to login to their OneDrive account. This launches the OAuth login, which succeeds and returns an access token and refresh token:

      final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(
        AuthorizationTokenRequest(
          // This is the Application (client) ID from Azure App Registrations:
          oauthConfiguration.oauthClientId,
          // This is my redirect URL, eg "com.someapp://oauthcallback/"
          oauthConfiguration.oauthRedirectUrl,
          serviceConfiguration: AuthorizationServiceConfiguration(
            authorizationEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
            tokenEndpoint: 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
          ),
          scopes: ['Files.ReadWrite.All','User.Read','offline_access'],
          externalUserAgent: ExternalUserAgent.ephemeralAsWebAuthenticationSession,
        ),
      );
    
  2. I then use this access token (using flutter_appauth which handles refreshing) to write a file to the user's OneDrive. This succeeds.

  3. I can then see the file in the web version of OneDrive, and GET the metadata for this file, no problem

  4. The problem is that I can't download the file content - I always get {"error":{"code":"unauthenticated","message":"Unauthenticated"}}

This is what I'm doing to download the file:

await httpClient.get(
  Uri.https(
    "graph.microsoft.com",
    "v1.0/me/drive/root:/path/to/file.txt:/content",
  ), 
  headers: {"Authorization": "Bearer $oAuthToken"},
)

I've also tried using the download URL directly (from the @microsoft.graph.downloadUrl field from the file's metadata), this yields the same result.

I'm struggling to understand why this is happening, and what I can do to solve this. I'm using Files.ReadWrite.All scope. I've verified that full read/write access is granted to the app on authentication via the user's Microsoft account privacy page:

enter image description here

I've verified that the App Registration in Azure with the Client ID that I'm using during the OAuth process has the same scopes which are being requested and returned to the app:

enter image description here

I've tried multiple devices, I've tried waiting for a few hours after the files have been uploaded to allow eventual consistency to catch up. I've tried using consumers oauth endpoint instead of common. The only clue I can get is I get this header with the error response:

www-authenticate: Wlid1.1 realm="WindowsLive", fault="BadContextToken", ...

Which suggests for some reason the access token (the same one I'm using to upload the file) is not valid to download the file. To repeat, this is for personal / consumer OneDrive only.

2
  • I just found a duplicate for my question but I can't vote to close as duplicate because my answer isn't upvoted or accepted yet, please see my answer there Commented Nov 3 at 13:09
  • It's not ideal to copy-paste content here; would you delete one of them? We can close either question, it doesn't have to be the first one (since in this case it did not attract an answer before you question). Commented Nov 3 at 21:12

1 Answer 1

0

I believe I have figured out the problem. I have managed to successfully download the file using a GET request to the downloadUrl retrieved from the file's metadata. The key apparently was to NOT pass my access token with the GET request. So, ironically, If I pass my access token I get access denied, if I don't pass any access token I can successfully download the file.

The docs do state that when downloading directly from the downloadUrl:

Pre-authenticated download URLs are only valid for a short period of time (a few minutes) and do not require an Authorization header to download.

I think maybe this should state instead that you MUST NOT send an Authorization header?

I'm still a bit confused as to why my GET request to https://graph.microsoft.com/v1.0/me/drive/root:/path/to/file.txt:/content fails with the same authentication denied error. My best guess is this: the docs state that when you use this method to download a file, you actually get a http Redirect response, which redirects the http client to the download URL. So I wonder if my http client (I'm using Cronet for Android) passes on the same headers (including my access token) from the original request to the redirected URL, which triggers the same problem as above - the presence of the access token is actually causing the access denied error. I'm not actually sure how to test what headers are sent in the redirected request so I can find out for sure, but it would make sense if that is the problem.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.