We have a Windows Application using MSAL with PublicClientApplicationBuilder to access SharePoint with the delegated permissions of the logged on user.
When our code is used with a login of a user who was invited as an external user in another AzureAD and his user is added to the members of a SharePoint site collection, we get an access token which results in HTTP 401. Using a user from the other AzureAD directly to log in does work. It is just with external user, we fail to get access.
When the user logs into SharePoint in the browser, using his external user login, he can access the other tenants SharePoint. So his external user account has permissions on that site collection, but it works only in the browser, not from our MSAL client.
Some details:
We created the app registration as multi tenant app in our AzureAD with the needed read and write permissions from the SharePoint delegated permission list.
An admin of the other AzureAD consented the delegated permissions for all users and we did the same in our AzureAD. So no matter which user tries to login and use the app will find consented permissions.
We use this code to get a public client app:
var clientAppId = "our-app-clientID";
var redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
_clientApp = PublicClientApplicationBuilder.Create(clientAppId)
.WithRedirectUri(redirectUri)
.WithLogging(Log, LogLevel.Verbose, false)
// .WithTenantId("we tried our and the other tenants ID")
.Build();
The commented line .WithTenantId was just added while we tried to find a solution.
We can see that it makes a difference for the token content. Without that line or with our tenantId we see the users "oid" is the objectId from the user in our AzureAD.
When we use the tenantId of the other AzureAD, we get the oid of the external user object in that other AzureAD.
So we had hopes that the latter call would succeed, but it fails as well, this time with HTTP 403. So the user token seems to get a bit further, but still not to the SharePoint site collection.
Any idea if this scenario is possible?
Would be nice, if possible and best if we would not have to call WithTenantId because otherwise we would some need to lookup the correct tenantId on the client machine - not sure where to get it from, except asking an admin from the other tenant and putting the value in some app config file or the Windows registry.
• The issue most probably according to your description suggests that the scopes might not be added correctly to the ‘Sharepoint API’ because since you are accessing the sharepoint site from a public client using MSAL authentication, the scopes/permissions to access the sharepoint site from a public client in Azure AD needs to be added and extended to service the requests from external user accounts also.
To do so, you need to add scopes/permissions to Sharepoint API through the Azure portal as when you are calling SharePoint APIs outside of the Microsoft Graph, you call ‘/_api/web/lists’ and it will retrieve all the lists. Thus, if your public client app performs this action using the Microsoft Graph API permissions then you will get an access denied error message as you are encountering HTTP 403 error.
• Thus, in your case, to access the sharepoint sites, you will need to add the scopes ‘AllSites.Read’ and ‘Sites.ReadWrite.All’ to the Sharepoint API for your public client app as the scope for ‘Sites.Read’ in the Microsoft Graph API isn’t enough for it. Also, ensure to add the ‘https://<domain>.sharepoint.com/AllSites.Write’ and ‘https://<yoursite>.sharepoint.com/Sites.ReadWrite.All’ scopes also to the Sharepoint API for that public client app.
To access the Sharepoint API through public client, you use AAD auth for which Microsoft recommends using a certificate rather than a secret. For more information regarding this, kindly use the below documentation link: -
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#client-credentials
• Also, ensure that ‘https://microsoft.sharepoint-df.com/Sites.Search.All’ and ‘https://yourtenant.sharepoint.com/Sites.Search.All’ are also added as scope in your application as Sharepoint online site might reject the token because of invalid audience.
For more information regarding the scope modification, kindly refer the below link for more details: -
https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/400
Related
I have created an API that is protected by OAuth using an app registration in Azure.
My app registration does not require assignment, but it exposes a number of roles that the underlying API verifies. To my understanding, this accomplishes almost the same thing as requiring approval.
So far I've only had user/group roles but now I've added an application role intended for integrators, and I want other application owners to be able to request permission to my API. I, as the API owner, would like to review these and either reject or consent to the request. E.g. I don't want everyone to be able to access my API within the tenant without my knowledge, just like all users/groups don't have access with me assigning them to a role.
The Role-based access control for application developers documentation makes it very clear who manages access:
...an application developer defines roles rather than authorizing individual users or groups. An administrator can then assign roles to different users and groups to control who has access to content and functionality.
However, if you create a role with allowed member types set to application, things are not quite as clear and it seems to behave more like a scope, where I give up any access management. Also from my limited understanding, a scope is used when the API needs to request data from the user (e.g. wanting to read their username), whereas a role is used for the application developer to control access to what they are developing.
This is what it looks like when I request access to my API from another app:
This same page mentions the following information:
The "Admin consent required" column shows the default value for an organization. However, user consent can be customized per permission, user, or app. This column may not reflect the value in your organization, or in organizations where this app will be used.
As well as:
Applications are authorized to call APIs when they are granted permissions by users/admins as part of the consent process
However, from my reading, it sounds like this never gives me, as the API owner, any insight into who has access to the API I own. I want to control application access the same way I'd assign a group or user to a role in the enterprise application.
Can this be achieved when it's an application on the other end, not a user? If not, how would I allow applications to integrate in a controlled manner?
I want to explain the feature Azure ad provided to protect web api here.
As you know, we usually use a token in the request header to let the api check if the request had correct permission to visit the api. Such as if the request from an allowed user role, right? So to whole progress should be authentication and authorization. Users sign in first then try to generate an access token to visit an api. Azure AD has similar architecture.
If you had a web application(e.g. web mvc app) you can integrate Azure AD into it then you can allow users use their user1#xx.onmicrosoft.com account to sign in. If you had a web api project, you can also integrate Azure ad and add [Authorize] attribute above the controller so that the incoming request should contain a correct Bearer token which we call it access token.
For Azure AD, we usually have 2 options, verification scopes or app roles. That results from the different flows we used to generate the access token. For example, we use auth code flow to sign in users and generate access token containing scp claim which is granted delegated api permissions. And we use client credential flow to let an application to generate access token containg roles claim which representing it's granted application api permissions. In short, when we set [Authorize] + [RequiredScope(scopeRequiredByApi)] in the controller, it allows requests from a user(user sign in the app and call api), when we set [Authorize(Roles = "roleRequiredByApi")], it allows requests from the application(no user signed in and the app call api by itself).
Here scopeRequiredByApi and roleRequiredByApi is what you exposed and then added to App Registration > Permissions. Just like Integrator you marked in the screenshot, it can be recognized as roleRequiredByApi because its type is Application.
And I'm afraid the roles is not what you want but to be honest what I said is what AAD can do for you... And I think the document I mentioned above about verification scopes or app roles will be a good sample for you.
The App Registration > Permissions section has a great feature for reviewing and limiting the access provided for your app registration:
enter link description here
In addition you should always define the scope of your permissions and limit it to the least required for your app. eg. NEVER set scope at the subscription level! Always set it at the resource group or lower.
Also note that Microsoft now provides Defender for APIs and you can use Sentinel to monitor a lot of the activities related to your app registration. Always always enable logging wherever possible and configure some method of alerting/reporting so you can better understand the activities for your app.
I have set up a Azure Active Directory App so that I can access the Microsoft Graph API with MSAL. However, I want to perform API calls without a user (https://learn.microsoft.com/en-us/graph/auth-v2-service) and as such I have added a few permissions that require "Admin consent" to my app. However, I cannot find a way to grant my app these permissions.
I've tried looking around the Azure portal for a way to grant these permissions but without success. I have also tried using the https://login.microsoftonline.com/{tenant}/adminconsent&... link to grant permissions, but unsucessfully so.
The response I received was
AADSTS500201: We are unable to issue tokens from this API version for
a Microsoft account. Please contact the application vendor as they
need to use version 2.0 of the protocol to support this.
I do not have an Azure subscription (not even the free one), but seeing as I was able to add apps to Azure AD as well as get access tokens and then make API calls on behalf of the authorized users I assumed I might not need a subscription.
I just made another app and now I have the grant consent button when I open the API Permissions view.
I have an application registration in Azure AD which has some Graph API delegated permissions set for it.
The application is simply a page in SharePoint that is making the Graph calls, authenticating with the ADAL.js library.
I now want to make an additional Graph call on the page to a new Graph API endpoint so I need to assign the application an additional permission. I set this permission in Azure AD and save. However, the user accessing the SharePoint page is never asked to reconsent to the new permissions, therefore the new Graph call fails with a 401 Unauthorised message.
I have tried this with a number of different endpoints and permissions, and I am sure I am setting the correct permissions in the application.
How can I have the consent dialog appear for users automatically when the application permissions change?
Admin consent is not an option.
I remember having a discussion about this with a member of Azure AD team some time back and this is what he had suggested at that time (though I never tried it so it may not work).
Essentially what he suggested is that when you get Unauthorized (401) error, you redirect the user to Azure AD login again i.e. follow OAuth authorization flow again but include prompt=consent in the sign in URI. This will present the user a consent page with latest permissions. Once the user consents to the latest permission, you can redirect the user back to your application. You may find this link helpful for understanding different values for propmpt parameter: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-openid-connect-code.
Again, I have not tried this personally so it may not work. If it doesn't, please let me know and I will remove my answer.
I've utilized the Partner Center REST API to provision tenants with orders/subscriptions. Now I want to start configuring the domains and users for the tenant I just created. My first step was to get a list of users using the Graph API https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/users-operations as a test expecting to see the admin account. If I pass in the domain of the reseller account in the request URL, it shows me the reseller users. But when I put in the domain of the account I just provisioned, I get Invalid domain name in the request url. I'm using the resellers AD token to do this. I'm confused as it gives me the option to specify a domain, but I can only access my own.
What credentials am I supposed to be using here? I tried to use the user/pass that was generated for the admin account from the provision, but I get unauthorized_client when trying to get an AD token from the Graph API.
It is hard to directly address your issues here since they are a little broad, and I would need more specific details about the various tenants you are working with, how your app is provisioned, permissions your app has etc...
However I think there a few principals you can follow which may help you debug your issues.
All AAD Authentication happens within the context of a specific tenant. This means whenever you get an access token for a resource, the scope of that token is limited to the boundaries of the tenant.
To authenticate with a client application in the context of a tenant, you must have the app registered in the tenant you are trying to access (line of business application / single tenant) or you have to make the app multi-tenant, in which case your app should be able to function in the context of any tenant... if the right provisioning has occurred.
Every tenant where your app is trying to function must have a service principal for the application provisioned in the tenant. This service principal represents your application's identity in the context of that tenant, and acts as a place to store the permission your application has in the context of that tenant. Most normally, this service principal gets provisioned into a tenant after a user from that tenant has consented to use the app as a part of the login experience.
If you are trying to use user context (authorization code grant flow) to retrieve details about a tenant, you must ensure that that user is present in the directory you are trying to query. For example a user U can exist in their home tenant T1. If you try to query another tenant T2 using that user account, you will get any number of errors describing that the user account does not exist etc. You can remedy this by creating a guest account for U in T2, in which case there will be a brand new user object created in T2 which links to the original user object in T1. None the less, the user object should always be present in the tenant you are trying to query.
If you are trying to sign into an application with a user account that is in T1 and T2, you need to be sure to specify the tenant you want to actually get the token for. By default, if you use the common endpoint, you will get a token for the users home tenant. However, it is perfectly valid to get a token for the secondary tenant, as long as you specify that to our Token Service when making the request.
Finally the client application you use to make these requests needs to have the right permissions to the Graph API if you want to make specific calls to the Graph API. Every tenant needs to individually consent to the application in their tenant context in order to provision the correct permissions to their application.
With those principals in mind:
The error you are getting with "unauthorized_client" seems to be an issue with application provisioning in the secondary tenant. Please make sure to first login to the application with a user from the secondary tenant, and make sure that user has the correct permissions to consent to your app (a tenant admin is best here).
For the second issue with "Invalid domain name in the request url" please try using some hints here.
Specifically this:
By using the myOrganization alias. This alias is only available when using OAuth Authorization Code Grant type (3-legged) authentication; that is, when using a delegated permission scope. The alias is not case sensitive. It replaces the object ID or tenant domain in the URL. When the alias is used, Graph API derives the tenant from the claims presented in the token attached to the request. The following URL shows how to address the users resource collection of a tenant using this alias:
https://graph.windows.net/myorganization/users?api-version=1.6.
I hope this puts you on the correct path to resolve most of your issues.
There are issues with sandbox accounts and Azure. Access to the Azure Management Portal for the sandbox isn't straightforward and at this time does not work properly. I had to create a free Azure account with my hotmail account, then link AD from the new account to my sandbox AD to bypass the bug. When adding a new directory to the new Azure account, select "Use existing directory", sign out, then sign into the sandbox account you want to link it to. Then create your app from the new account.
After getting my app setup properly and new credentials, I had to enable pre-consent with the instructions listed at the end of: https://github.com/Microsoft/Partner-Center-Explorer
Lastly, I had to login to the Graph API with the customers ID, but with the resellers credentials.
The scenarios on the Partner Center SDK website include a section "Manage user accounts and assign licenses" under the "Manage customer accounts" section.
These samples include creating users and assigning licenses and a link to a console test app.
As an aside, a new version of the Partner Center SDK has just become available here. It was released on July 5th. While there is no official change history that I can find, I can see that it includes some new classes such as CustomerUser. You may find it easier to use that library rather than hitting the REST API (depending on how much work you've already done).
I have a scenario where userA shares a file with userB on OneDrive for Business. They both belong to the same Office365 tenant.
Is there a way to programmatically retrieve shared file content using userB identity (OAuth access token)? I've tried using 2.0 API syntaxt, which works for userA, but I'm getting 401 error when trying to execute this request with userB access token, even though file was shared with userB.
https://{tenantid}-my.sharepoint.com/_api/v2.0/drives/{driveId}/items/{itemId}/
Or maybe there is a way I could use Azure AD Application delegated permission to access this file on behalf of its owner having driveId and itemId?
As far as I know, this feature is not supported yet.
There is a request for this functionality on UserVoice which you can vote on.
As for March 2016, the only way I was able to retrieve shared file content programmatically from OneDrive for Business was by:
Creating SharePoint Provider hosted addin with FullControl permission for the whole Tenant
Registering the app under https://{tenantid}-my.sharepoint.com/_layouts/15/AppRegNew.aspx
Creating app catalog on SharePoint Online and publishing the app to the catalog
Installing app on at least one SharePoint site collection within the Tenant
Using ClientSecret and ClientId executing following code from http://blogs.msdn.com/b/vesku/archive/2015/01/05/customizing-onedrive-for-business-sites-with-app-model.aspx
Uri url = new Uri("https://{tenantid}-my.sharepoint.com/personal/{user}");
string realm = TokenHelper.GetRealmFromTargetUrl(url);
var token = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal,url.Authority, realm).AccessToken;
token variable contains AccessToken that can be used as Bearer token in OneDrive for Business API requests (e.g. getting files from other users 'OneDrives').
https://{tenantid}-my.sharepoint.com/_api/v2.0/drives/{driveId}/items/{itemId}/