MS Graph, daemon app 401 unauthorized on Files.ReadWrite.All calls - azure-active-directory

UPDATE: I've received notice from Microsoft that this problem is a bug in the Graph API. They're working on a solution.
I'm using the new v2.0 OAuth flow to authenticate my app for use with Microsoft Graph to make it able to list any users files, download and upload files in any users OneDrive and set permissions to files. This without the user being logged in, that is running it as a service account/daemon.
I've set up a new "Converged application" in the new Application Registration Portal. I've set all necessary scopes/application permission, including Files.ReadWrite.All. (I actually checked all possible boxes...). In the Microsoft Graph docs this should be the only scope necessary when calling the endpoints I'm interested in:
/v1.0/users/{userID}/drive
/v1.0/users/{userID}/drive/items/{ItemID}/children
/v1.0/users/{userID}/drive/items/{ItemID}/content
/v1.0/users/{userID}/drive/items/{ItemID}/invite
/v1.0/users/{userID}/drive/items/{ItemID}/createLink
Then I've followed the documentation for the Client Credentials flow, including giving Admin Consent to the app for use in my company tenant.
I'm successfully receiving an access token. After receiving the access token I've double checked at jwt.io that the token actually contains all scopes (incl. Files.ReadWrite.All).
I'm able to use this access token to get any user's drive and list any users files (the first two endpoints listed above). I've also tried to get thumbnails of any users files which works fine. But as soon as I try to download a file, add permissions to a file or create a Sharing Link (the last three endpoints listed above), I receive an 401 Unauthorized error. From this, I assume the scope Files.Read.All works fine, but the scope Files.ReadWrite.All is not working.
As to what I can understand from the Scopes documentation, the scopes I'm trying to use should work. It the "App-only permissions requiring administrator's consent" section, it describes Files.ReadWrite.All as:
Allows the app to read, create, update and delete all files in all site collections without a signed in user.
I've hit a wall. Are there limitations to the new v2.0 OAuth token and/or Microsoft Graph regarding App-Only access that I'm missing?

Closing the loop for those who stumble on this question. There was an issue with Files.ReadWrite.All in App-Only scenarios when it came to uploading or changing permissions of a file.
The issue with downloading is unrelated. Authorization errors when downloading a file stem from passing an Authorization header in the download request. The `/content/ endpoint returns a URL that can be used to download the file. This is a pre-authorized URL that exists for a short period of time. Passing an Authorization header in that request results in an error since it doesn't expect to receive such a header, nor can it determine which credentials it should use (super-oversimplification but this the general idea).

Related

What's the difference between User.Read and OpenID+Profile+Email scopes

Does User.Read "contain" the permissions email openid and profile? I've found that apps that are requesting the 3x scopes, can instead accept just the User.Read permission and still function equivalently
At work I'll get requests from the business to help them setup SSO using OIDC, and I'm not actually sure what permissions I should be giving them. Seems like either option works but I'd like to better understand what's happening
See my observations below:
I've created a basic Function App, and configured it to use OpenID Connect Image
My App Registration already has the User.Read permission with admin consent, so when I log into my Function, there's no issue.
Image
However, after removing the User.Read permission and logging in, I now get a permissions request prompt Image
And after consenting to the permissions, I can now see that email openid and profile permissions were added Image
Even more interesting, the permissions in the request prompt correlate to openid and offline_access, but offline_access wasn't added, while email and profile weren't in the request
I did find a similar question, but the accepted answer doesn't seem to align with what I see here
Generally I would favour the OAuth standard design where fields like these are Personally Identifiable Information (PII). So each app should only use the smallest scope it needs, as an information disclosure best practice. See also this Curity article.
Name
Email
Phone
Address
The Graph API can also be used with standard scopes, as in step 11 of this blog post of mine, where I wanted to get hold of user info in an API. So if this works for you I would prefer it. Personally I also prefer standard scopes so that my application code is portable.
Microsoft's design is based on each API requiring a different access token, the resource indicators spec. It is interesting, though perhaps not always intuitive. I am no expert on Azure AD though, and there may be some intended usage I do not understand.
User.Read is a scope intended to be used when requesting an access token for the Microsoft Graph API. It grants privileges to read the profile of the signed-in user only. A separate call to the Microsoft Graph API is required to retrieve the profile.
openid, email, profile and offline_access are OpenID Connect scopes:
openid is used to request an id token.
offline_access is used to request a refresh token which can later be used to get a new access token.
email to request an email claim.
profile to request several user claims (Eg.preferred_username).
Both email and profile can be used to augment information available in the UserInfo endpoint, however, it is recommended to use the id token which is already a superset of the information available at the aforementioned endpoint.

How to set up access to Azure blob storage from React app (deployed in Azure web app) with credentials from browser?

I got stuck on trying to access Azure blob storage from React app (thus from browser) with credentials.
And firstly I want to admit, that I am newbie in Azure, so maybe I misunderstood some basic concepts...
My current situation and goal:
I am developing React app (lets say MyReactApp). This app uses some files from Azure blob storage (lets say MyBlobStorage) -> it reads, creates and deletes blobs.
I started to develop it on my local and for this dev purpose I was connecting to MyBlobStorage with SAS - this worked perfectly.
MyReactApp is browser only, so it does not have any backend.
After finishing local development, I deployed it as Azure web app with SAS. What have I done:
created App service (lets say MyAppService)
register app in Azure Active Directory and use it as Identity Provider in MyAppService
After this the app works from Azure url perfectly too.
But my app on Azure should fulfill 2 conditions:
User has to log in with AAD account before access to MyReactApp
App itself must get rid of SAS (because it is not secure as it can be obtained from browser) and use some Azure credentials to connect to Azure blob storage
First condition - user log in:
I enabled "easy" logging in MyAppService -> Authentication and chose users, who can have access.
in Authentication section of app in AAD I set up Web type Redirect Uri as /.auth/login/aad/callback
Still everything works great - the user, who is assigned to the app, can log in and work with the app - so far so good, but now the problem comes
Second condition - I wanted to get rid of the SAS to access MyBlobStorage and use DefaultAzureCredentials:
I turned on managed identity for MyAppService and add it as Storage Blob Data Contributor for MyBlobStorage
I obtained user_impersonation and User.Read Api permissions for my app
I removed SAS and tried to add DefaultAzureCredentials to my code -> but it seems, that they can't be used in browser and only option is InteractiveBrowserCredentails
so I tried to use InteractiveBrowserCredentails like this:
this.interactiveBrowserCredential = new InteractiveBrowserCredential({
tenantId: "<appTenant>", // got from app registration on AAD
clientId: "<appClient>", // got from app registration on AAD
redirectUri: <MyAppServiceURi>/.auth/login/aad/callback // the same as in Azure AAD app,
});
this.blobSC = new BlobServiceClient(Constants.STORAGE_PATH, this.interactiveBrowserCredential);
My current result:
This shows login popup after getting to the page and after another signing in it results in error:
AADSTS9002326: Cross-origin token redemption is permitted only for the
'Single-Page Application' client-type.
I googled it of course and according to some answers I tried to change the Web type of redirect URI to SPA.
I tried it, but some other error showed up:
AADSTS9002325: Proof Key for Code Exchange is required for
cross-origin authorization code redemption.
Surprisingly this should be solved by changing from SPA to Web type...:) So I am trapped...
My expected result
In ideal world, I want to connect MyReactApp to MyBlobStorage without popup and with some secret credentials, just to say something like this.blobSC = new BlobServiceClient(Constants.STORAGE_PATH, credentials);
and be able to work with blobs.
Is it possible to access blob storage from browser without errors of course and ideally without popup, which needs another log in for user?
My complementary questions
Can I use somehow the logging info from the user (from his "easy" AAD logging)? I can get his info with GET call to /.auth/me, maybe it can be utilized, so I can use his info to access the blobs?
The solution should be working on localhost too (I tried to add http://localhost:3000/ to redirect uri, but without success), can it be done?
Thank you all, who read the whole story!

Authorization request to get access on behalf of a user results in a 'cookiesDisabled' page

I'm trying to do an Authorization request following the documentation about it on Microsoft (Getting access on behalf of a user: https://learn.microsoft.com/en-us/graph/auth-v2-user). I'm making the request using Azure's Logic Apps. I already made an app registration in Azure AD and gave it the following permissions (I used the app for a few different requests before so that's why it contains a lot of unnecessary api permissions). I already succeeded in getting access without a user (https://learn.microsoft.com/en-us/graph/auth-v2-service). Now I really don't know what I'm doing wrong, so if anybody has an idea of what it is, please let me know. I will try to explain as carefully as possible using screenshots so you guys get the idea of what I'm trying to do.
On the first screen below you can see the api permission I added to my app registration. For this request I'm only asking authorization for the one with arrow next to it(as you will see later on).
On the second screen you can see the HTTP post request I'm trying to make to the authorize endpoint. I blurred out the Tenant and Client_id for privacy reasons. I only added the required parameters in the body as described by Microsoft. In the scope parameter you can see the api permission I'm asking permission for.
On the third screenshot you can see the output of the request. Instead of getting an Authorization code as requested, I get an HTML body.
When I paste that HTML body into a browser it gives me the following result:
I have no clue what I'm doing wrong. I tripled checked to make sure cookies are enabled, made sure third-party cookies are not disabled and added login.microsoftonline to my trusted websites.
I'm starting to think I'm doing something very simple wrong, but I can't figure out exactly what. Any help is welcome! :D
Sorry can't add a comment so posting as an answer
What you are trying to implement is the Authorization Code grant flow of OAUTH 2.0. In Authorization code grant flow following steps occur
1) User is presented with the scopes that an application requires when accessing certain resources,
2) The user authorizes this. and the user is redirected to a redirect url
3) The application then exchanges the code sent with the redirect url to get the actual token which in this case will be sent to the Microsoft Graph for validation.
4) User then sees the information pulled.
The major crux of Authorization Code grant flow is that "User Authorization is required" This basically means that this flow is used when the call is invoked from a browser client where the user is actually interacting. This flow should not go through the Azure Logic Apps. If you want a service or a daemon to access the resources in that case you should use Client Credentials Grant flow

Trying to access a v2 endpoint hosted webapi but no luck, true if only graph api works on v2 now?

Had a webapi running on v2 endpoint, the intent was to get access through a single call to both graph and the custom webapi, was using the v2 auth code grant flow, the url using as below,
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=14e9111f3e1-d220-415d-9bf4-d089f0b5feff&response_type=code&redirect_uri=https%3A%2F%2Flocalhost%3A8081%2Fartifactory%2Fwebapp%2Fsaml%2FloginResponse&response_mode=query&scope=api%3A%2F%2F14e9f3e1-d220-415d-9bf4-d089f0b5feff%2Faccess_as_user%20https%3A%2F%2Fgraph.windows.net%2Fuser.read%20openid%20offline_access&state=12345
with the scope as
api://14e9f3e1-d220-415d-9bf4-d089f0b5feff/access_as_user https://graph.windows.net/user.read openid offline_access
However, keep failing with a invalid scope error. If I take out the custom webapi from the resource, everything went through wonderfully.
Reading further, there is a limitation for webpi that
Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID.
So I am confused, how to archieve the goal to use v2 endpoint to authenticate and get access to both graph and webapi????
--edit
the error message is 'AADSTS65005: The application 'blah' asked for scope 'user.read' that doesn't exist on the resource. Contact the app vendor.'
Today the v2 endpoint cannot issue an access token for a custom API. The feature is in active development, but there's no ETA to share.
Also note: even when the feature will be available, you will not be able to reuse the same access token across multiple resources; you'll be able to consent for multiple resources at once, so that your user is only promoted once, but you will need to request access tokens for each resources separately.

Google Calendar API FreeBusy Access Using Service Account

We have built an application that is attempting to use a Service Account to access the google Calendar API. We manage the application ourselves, but the calendars we are trying to access are on a separate GSuite account. We have had the GSuite admin follow the steps here to allow access to our app based on client ID and the auth scope of "https://www.googleapis.com/auth/calendar.readonly."
Now when using the service account to attempt to access user calendar data, I'm a bit confused about how to properly format the request. Specifically, if I don't include a "sub" parameter, the request works in the sense that I am granted a token and I'm able to proceed to make a subsequent request to access calendars. However in that subsequent request, I am returned with errors on users calendars indicating not found i.e. I don't have access. Below is the request:
String jwtClaimset = '{'+
'"iss":"xxxxx#service-account.com",'+
'"sub":"xxxxxx#xxxx.com",'+
'"scope":"https://www.googleapis.com/auth/calendar.readonly",'+
'"aud":"https://www.googleapis.com/oauth2/v4/token",'+
'"exp":'+expTime+','+
'"iat":'+requestTime+
'}';
However, then if I include a specific email/username from the GSuite account to "impersonate", I am not able to request the token at all, I'm presented with the below error:
"error_description": "Client is unauthorized to retrieve access tokens using this method."
So my question is, do I need the sub parameter to access any of the users calendars in the GSuite and if so do you know why that error is being presented?
And I have no idea what happened maybe it just took time for the GSuite permissions/access to update but now this is working.
For note, we are using the sub parameter and its working using that (as we suspected we needed).

Resources