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!
Related
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 am trying get access token without auth code, so using below method to get it. but i am facing issue as "the request body must contain the following parameter 'client_secret or client_assertion'"
Can you suggest necessary pointers on this. Running this in console application.
try
{
// Use the 'Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory' Nuget package for auth.
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult authResult = authContext.AcquireTokenAsync(resourceId, clientId, new UserCredential(crmAdminUserName, crmAdminPassword)).Result;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Assuming the app is registered in the portal, and you know the client id, client secret key/app key, authority and audience
Then this code snippet will get you the access token
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential clientCredential = new ClientCredential(clientId, clientkey);
AuthenticationResult authenticationResult = await authContext.AcquireTokenAsync(ResourceUrl, clientCredential);
Resource Id/Resource Url e.g. https://manage.windowsazure.com/{placeholder-for-your-azure-ad-tenant-name}.onmicrosoft.com
AcquireTokenAsync documentation is available from here
AuthenticationContext class documentation is available from here
There are two kinds of clients on Azure AD, one is public and the other is confidential which requires provide secret when we acquire the token.
To fix this issue, you can register a public client(native client application) in this scenario.
Here is a helpful document about integrating applications with Azure active directory.
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
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.