I'm using the Microsoft Graph SDK to get an access token for my application (not a user) in order to read from sharepoint. I've been following this document, as well as posted this SO question. The code in the linked SO is the same. I was able to add application permissions as well as grant them (by pressing the button) in azure portal. The problem is, the token that comes back to be used does not contain any roles / scp claims in it. Therefore when using the token, I get the "Either scp or roles claim need to be present in the token" message.
Just to be certain, the only value for my scope that I pass when getting the access token is: https://graph.microsoft.com/.default. I don't pass anything else like Sites.ReadWrite.All (I get an exception if I add that scope anyway). I'm not sure how to continue troubleshooting and any help would be appreciated.
Edit: added code using the graph SDK shown below:
var client = new ConfidentialClientApplication(id, uri, cred, null, new SessionTokenCache());
var authResult = await client.AcquireTokenForClientAsync(new[] {"https://graph.microsoft.com/.default"});
var token = authResult.AccessToken;
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(async request => {request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token)}));
var drives = await graphServiceClient.Sites[<sharepoint_host>].SiteWithPath(<known_path>).Drives.Request().GetAsync();
Seems like doing the app initialization in a different way is the solution. Instead of this:
var client = new ConfidentialClientApplication(id, uri, cred, null, new SessionTokenCache());
do this:
var app = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, credentials, null, new TokenCache());
The problem is, the token that comes back to be used does not contain
any roles / scp claims in it.
If you can not find any roles/scp claims in the decoded access token. You need to check the permission in Azure portal again.
The decoded access token should contain the roles you granted.
Login Azure portal->click Azure Active Directory->click App registrations(preview)->find your application.
Click your application->API permissions->check if you have grant admin consent for your application. If not, click 'Grant admin consent'.
The code for getting access token. You can find more details here.
//authority=https://login.microsoftonline.com/{tenant}/
ClientCredential clientCredentials;
clientCredentials = new ClientCredential("{clientSecret}");
var app = new ConfidentialClientApplication("{clientId}", "{authority}", "{redirecturl}",
clientCredentials, null, new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
result = app.AcquireTokenForClientAsync(scopes).Result;
Console.WriteLine(result.AccessToken);
Related
I am trying to manipulate microsoft planner tasks (end goal is to create a task in a certain Scope and bucket).
I am already failing at listing a Plan or the buckets for a plan. I want to make this connection from a background service (daemon) so no interactive user login should take place. (with interactive login credentials i can make it work, but that's not what i need/want).
So i Created a new App Registration in Azure with the Api Permissions:
Group.Read.All (Delegated)
Group.ReadWrite.All (Delegated)
Tasks.Read (Delegated)
Tasks.Read.Shared (Delegated)
Tasks.ReadWrite (Delegated)
Tasks.ReadWrite.Shared (Delegated)
User.Read (Delegated)
Group.ReadWrite.All (Application)
Tasks.ReadWrite.All (Application)
User.ManageIdentities.All (Application)
User.ReadWrite.All (Application)
I also checked the "Allow public client flows" setting on the App registration Authentication tab.
I started by adding the ones prescribed on the official microsoft doc website about this topic. And then started adding some because i was still receiving Access Denied messages. Thus reaching this list. It should be enough according to microsoft.
Then i have this code to authenticate with Microsoft graph, giving me a graphclient instance which is successfully initialized:
private GraphServiceClient initializeTeamsGraphConnection(string TenantId, string ApplicationId, string ClientSecret)
{
// The client credentials flow requires that you request the
// /.default scope, and preconfigure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { ScopeGraph };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = TenantId;
// Values from app registration
var clientId = ApplicationId;
var clientSecret = ClientSecret;
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
return graphClient;
}
So authentication seems to be succesful, but when i then try to list a plan using the code below:
private void CreateTask(GraphServiceClient client)
{
var graphTask = client.Planner.Plans["Sdonp-JNB0aInPxDcxMowZgACZ59"]
.Request()
.GetAsync();
while (!graphTask.IsCompleted)
{
graphTask.Wait(10000);
}
var plans = graphTask.Result;
I get following error:
403 - Forbidden: Access is denied.
You do not have permission to view this directory or page using the credentials that you supplied.
Access Permissions should be well above what is needed to do this. Any idea on what I am doing wrong?
Again this code is working because when i change authentication to some sort of interactive login type, i get this plan info no problem
Planner API currently supports only delegated permissions that's the reason why it returns 403 for daemon (background service).
According to this announcement, support for application permissions is coming soon.
On this page https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#IntegratedWindowsProvider it is said "The interactive flow is used by mobile applications (Xamarin and UWP) and desktops applications to call Microsoft Graph in the name of a user."
So I developed a C# console app to login and query some data:
var clientId = "<APP GUID GOES HERE>";
var tenantId = "<APP TENANT GUID GOES HERE>";
var scopes = new[] {"user.read","Calendars.Read"};
var clientApplication = PublicClientApplicationBuilder
.Create(clientId)
.Build();
var authProvider = new InteractiveAuthenticationProvider(clientApplication, scopes);
var graphClient = new GraphServiceClient(authProvider);
User me = graphClient.Me.Request()
.GetAsync()
.Result;
During running the console app a login "page" comes out, I entered my credentials, but at the end the pagse says error "AADSTS500113: No reply address is registered for the application.", and the code got "user cancelled the login"
BTW: I dont want to login manually each time, I added my password to the code:
var scopes = new[] {"offline_access","user.read","Calendars.Read"};
var clientApplication = PublicClientApplicationBuilder
.Create(clientId)
.Build();
var authProvider = new UsernamePasswordProvider(clientApplication, scopes);
var graphClient = new GraphServiceClient(authProvider);
var pwd = ConvertToSecureString("<MYPASSWORD GOES HERE>");
User me = graphClient.Me.Request()
.WithUsernamePassword("<MY EMAIL GOES HERE>", pwd)
.GetAsync()
.Result;
In this case no login page shows up (good), but an exception raises: "The grant type is not supported over the /common or /consumers endpoints. Please use the /organizations or tenant-specific endpoint."
Then I added a WithTenantId(...) to the Build(), now I got different exception: "MsalUiRequiredException: AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000003-0000-0000-c000-000000000000'." but the multi-factor auth request does not come to my phone.
What goes wrong? What should I do to get this app work?
What I want is to execute this c# console app regularly on my desktop computer, without any interactions (logins) as my user to query some data using graph api. How to do that correctly?
Thanks in advance!
This error AADSTS500113: No reply address is registered for the application indicates that the reply URL is not available and AAD does not know where to send the token. To fix this, you need to add a valid redirect URI in your app registration in AAD.
The next error : MsalUiRequiredException in your case happens because the user needs to perform multiple factor authentication based your Azure AD policies. To do this, you need to change your flow from the current username/password provider to interactive authentication provider since in the former case, users who need to do MFA won't be able to sign-in (as there is no interaction).
This would look something like this:
IPublicClientApplication publicClientApplication = PublicClientApplicationBuilder
.Create(clientId)
.Build();
InteractiveAuthenticationProvider authenticationProvider = new InteractiveAuthenticationProvider(publicClientApplication, scopes);
You can then acquire the token interactively :
string[] scopes = new string[] {"user.read"};
var app = PublicClientApplicationBuilder.Create(clientId).Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result;
try
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch(MsalUiRequiredException)
{
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
To authenticate without the user, your app can implement client credentials acquisition methods - these suppose that the app has previously registered a secret (application password or certificate) with Azure AD, which it then shares with this call. Please note that no user interactions means you can't use delegated permissions.
Let me know if this helps and if you have further questions.
I have a C# solution that I use to retrieve Mails via Microsoft Graph API.
Currently I get the access token interactively via
var pcApplication = PublicClientApplicationBuilder.Create(clientId).Build();
AcquireTokenInteractiveParameterBuilder acquireTokenInteractiveParameterBuilder =
pcApplication.AcquireTokenInteractive(scopes);
acquireTokenInteractiveParameterBuilder.WithLoginHint(login);
acquireTokenInteractiveParameterBuilder.WithAuthority(authorityUri);
AuthenticationResult authResult = await acquireTokenInteractiveParameterBuilder.ExecuteAsync();
and aquire a new token silently with existing token like this:
var pcApplication = PublicClientApplicationBuilder.Create(clientId).Build();
AuthenticationResult authResult = await pcApplication.AcquireTokenSilent(scopes, login).ExecuteAsync();
My scopes are { "Mail.ReadWrite", "Mail.ReadWrite.Shared", "Mail.Send" }.
I do not want to use Application permissions, but User delegated permissions.
Now I want to access a resource as an impersonated user in form of technicalUser#domain\impersonatedUser. But when I want to call graphclient.Users[User] where User is my impersonated user with the token Cache of the user, I get an error message saying I have not the right permissions.
Am I missing something in my scope or am I missing something in my code with the token flow?
So I'm trying to implement persistent tokens for our office authentication so that the user does not have to sign into office each time they are in a new session. The code I currently have to authenticating the user is as below.
string authority = "https://login.microsoftonline.com/common";
var tokenCache = new ADALTokenCache(User.Identity.GetUserId());
AuthenticationContext authContext = new AuthenticationContext(authority, tokenCache );
var token = authContext.AcquireTokenSilentAsync(scopes, clientId, new UserIdentifier(userId, UserIdentifierType.RequiredDisplayableId));
But everything I've tried so far gives me the error below
The Exception is: "Failed to acquire token silently. Call method AcquireToken"
The method Im using to aquire the token in the first place is as below
string authority = "https://login.microsoftonline.com/common";
var fileCache = new ADALTokenCache(User.Identity.GetUserId());
AuthenticationContext authContext = new AuthenticationContext(authority, fileCache);
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, redirectUri, credential, scopes);
And the token cache im using is a db implementation which I made from a tutorial which I cannnot find again, if I watch the db I can see that new tokens are being inserted into the db when AcquireTokenByAuthorizationCodeAsync is called.
Update:
This is my result from authResult when calling AcquireTokenByAuthorizationCodeAsync
I have marked Virbonet's answer as the solution but I have not fixed it but he did explain to me where I was going wrong
AcquireTokenSilent cannot work if you are passing /common in the authority. Using "common" is equivalent to declaring that you don' know what tenant is the user from, hence ADAL cannot return a cached token form a specific tenant - user interaction is required to determine which tenant should be used.
If you want to call AcquireTokenSilent you need to initialize the authority with the exact tenant of the incoming user, as in "https://login.microsoftonline.com/"+tenantID here tenantID is the tenantID from the current ClaimsPrincipal.
This is the function call you need to use: AcquireTokenByAuthorizationCode() but not AcquireTokenSilent().
Hope this helps.
I am trying to download a user's mailbox using the Email Audit API. I am getting a 403 Forbidden response to this code (the error occurs on the last line, the call to the UploadPublicKey method):
var certificate = new X509Certificate2(System.Web.HttpRuntime.AppDomainAppPath + "key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://apps-apis.google.com/a/feeds/compliance/audit/" }
}.FromCertificate(certificate));
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
DebugLabel.Text = credential.Token.AccessToken;
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
AuditService aserv = new AuditService(strOurDomain, "GoogleMailAudit");
aserv.RequestFactory = requestFactory;
aserv.UploadPublicKey(strPublicKey);
I have created the service account in the Developers Console and granted the Client ID access to https://apps-apis.google.com/a/feeds/compliance/audit/ in the Admin console.
Seems to me like the account should have all the permissions it needs, yet it doesn't. Any idea what I am missing?
OK, so I gave up on trying to make it work with a service account even though that is what Google's documentation would lead you to believe is the correct way to do it. After emailing Google support, I learned I could just use OAuth2 for the super user account that created the application on the developer's console.
So then I worked on getting an access token for offline access (a refresh token) by following the process outlined here:
Youtube API single-user scenario with OAuth (uploading videos)
and then taking that refresh token and using it with this code:
public static GOAuth2RequestFactory RefreshAuthenticate(){
OAuth2Parameters parameters = new OAuth2Parameters(){
RefreshToken = "<YourRefreshToken>",
AccessToken = "<AnyOfYourPreviousAccessTokens>",
ClientId = "<YourClientID>",
ClientSecret = "<YourClientSecret>",
Scope = "https://apps-apis.google.com/a/feeds/compliance/audit/",
AccessType = "offline",
TokenType = "refresh"
};
OAuthUtil.RefreshAccessToken(parameters);
return new GOAuth2RequestFactory(null, "<YourApplicationName>", parameters);
}
which is code from here https://stackoverflow.com/a/23528629/5215904 (Except I changed the second to last line... for whatever reason the code shared did not work until I made that change).
So there I was finally able to get myself an access token that would allow me access to the Email Audit API. From there everything was a breeze once I stopped trying to mess around with a service account.