I have a native app which i'm using in a multi-tenant scenario.
To authenticate the user -- and to get their consent on allowing this application to access Azure on their behalf -- I simply instantiate an AuthenticationContext and call AcquireTokenAsync. However I don't know how if this by default uses the AdminConsent or not? If not how can i achieve that?
Below is the sample code that i use:
AuthenticationContext commonAuthContext = new AuthenticationContext("https://login.microsoftonline.com/common");
AuthenticationResult result = await commonAuthContext.AcquireTokenAsync(resource,
clientId, replyUrl,
new PlatformParameters(PromptBehavior.Always));
No, this does not automatically invoke admin consent (even if an admin consents, they're just consenting for themselves, not for the whole tenant).
To invoke admin consent, you have to add prompt=admin_consent to the authentication request:
AuthenticationResult result = await commonAuthContext.AcquireTokenAsync(
resource,
clientId,
replyUrl,
new PlatformParameters(PromptBehavior.Auto), // <-- Important: use PromptBehavior.Auto
UserIdentifier.AnyUser,
"prompt=admin_consent"); // <-- This is the magic
Of course, you should not send all users to sign in with this, as it will fail if the user is not an admin.
See "Triggering the Azure AD consent framework at runtime": https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/#triggering-the-azure-ad-consent-framework-at-runtime
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.
I registered new application as Web app / API (not native), added permission to Access Dynamics 365 as organization users.
I following this guide (https://code.msdn.microsoft.com/simple-web-api-quick-start-e0ba3d6b) which has the below code, the only difference is that I have updated my Microsoft.IdentityModel.Clients.ActiveDirectory library which required small code change.
//Obtain the Azure Active Directory Authentication Library (ADAL)
AuthenticationParameters ap = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(serviceUrl + "api/data/")).Result;
AuthenticationContext authContext = new AuthenticationContext(ap.Authority, false);
//Note that an Azure AD access token has finite lifetime, default expiration is 60 minutes.
AuthenticationResult authResult = authContext.AcquireTokenAsync(
serviceUrl, clientId, new Uri(redirectUrl),
new PlatformParameters(PromptBehavior.Always)).Result;
When I run this I getting a popup where I fill in my credentials and then it throws this error:
AdalException: {"error":"invalid_client","error_description":"AADSTS70002: The request body must contain the following parameter: 'client_secret or client_assertion'.\r\nTrace ID: xxx\r\nCorrelation ID: xxx\r\nTimestamp: 2018-06-28 10:17:20Z","error_codes":[70002],"timestamp":"2018-06-28 10:17:20Z","trace_id":"xxx","correlation_id":"xxx"}: Unknown error
I tried to add the client_secret by applying the change below but it still doesn't work
AuthenticationResult authResult = authContext.AcquireTokenAsync(
serviceUrl, clientId, new Uri(redirectUrl),
new PlatformParameters(PromptBehavior.Always), UserIdentifier.AnyUser,
$"client_secret={clientSecret}").Result;
But when I run this it does work, but this is not what I want, I want to login with specific user.
AuthenticationResult authResult = authContext.AcquireTokenAsync(
serviceUrl, new ClientCredential(clientId, clientSecret)).Result;
The Client Credential and Client Assertion authentication flows are meant for service to service communication, without user involvement. So your Web Api would access Dynamics not in the context of a user, but as itself.
Have a look at the official wiki to understand more: https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Client-credential-flows
Also, please be aware that we cannot help you if you make changes to Microsoft.IdentityModel.Clients.ActiveDirectory. You'd also miss out on updates, some of which are security critical. But feel free to propose changes if you think others would benefit!
I am working on client project where I need to get the user from Azure AD and need to store in the application database. For that, I have added the page to get the settings and button to test settings details. I am using below code to get the access token
string authString = authnEndpoint + tenant;
AuthenticationContext authenticationContext = new AuthenticationContext(authString);
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(resource, clientCred);
authenticationResult.AccessToken
It is working fine in below scenario
When entering the wrong details I am getting the exception on AcquireToken method whereas when I have given correct details it gives me access token
It is not working in below scenario
It is not working fine in reverse order that is When entering the correct details I am getting access token after that I am entering wrong details now it give me access token again. I can resolve this only when I am restarting the application
How to solve this issue?
You are using the constructor AuthenticationContext(String) which have the Token Cache enabled by default. Hence, it will give you the token even if your inputs are not correct, within certain a mount of time. Here is a solution.
AuthenticationContext authenticationContext = new AuthenticationContext(authString, null);
Use constructor AuthenticationContext(String, TokenCache) instead. Setting TokenCache to be null, will disable the Token Cache.
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 have an application that needs to support both Federation (IdP-Initiated) as well as manual authentication (standard username/password form). As such I am using .NET v4.5 System.Identity to make the application claims aware.
The issue we are seeing in dev is that anytime an AppPool recycle happens (like a recompile) and we reload the page or take any other action we get an error trying to access any of our custom claims. It's as if the user is still authenticated, but our custom claims are totally gone. In order to keep working, we need to close all instances of the browser and login again. This can obviously happen in the wild and is something we cannot have happen (horrible end user experience).
Is there something we are doing wrong or a way that we can trap/detect this condition and force the user to log back in again?
Background
In the case of a manual login, we build a CustomClaimsIdentity instance that gets passed into a new ClaimsPrincipal which is then used to create a new SessionSecuirytToken instance and then written out as follows:
var claims = CustomClaimsAuthenticationManager.BuildClaimsList( user );
var identity = new UniversalIdentity( claims, AuthenticationTypes.Password );
var principal = new ClaimsPrincipal( identity );
var token = new SessionSecurityToken( principal, TimeSpan.FromMinutes( user.Customer.SessionExp ?? 120 ) );
var sam = FederatedAuthentication.SessionAuthenticationModule;
sam.WriteSessionTokenToCookie( token );
In the case of Idp-Initiated logins, we handle the FederatedAuthentication.WSFederationAuthenticationModule.SignedIn event and perform checks to validate the supplied claims as well as build up the custom claims our app adds to the identity the same way we do for manual authentication.
You can safely cast an IPrincipal to ClaimsPrincipal:
ClaimsPrincipal cp = (ClaimsPrincipal)Thread.CurrentPrincipal;