I have a console application registered in Azure AD that connects to CRM Online (configured using these steps). It queries the Web API.
The application needs to run with no user interaction... but unfortunately the call to AcquireTokenSilentAsync always fails and only AcquireTokenAsync works. This makes a user login dialog appear which fails the user interaction requirement!
Is there any way to prevent this prompt, either by saving the login somewhere on the client machine (which hasn't worked so far) or perhaps using a certificate (but how do you do this?) or something else?
I'm using the ADAL for .NET v3.10.305110106 release. The following code is used to authenticate:
private static async Task PerformOnlineAuthentication()
{
_authInfo = new AuthInfo(); // This is just a simple class of parameters
Console.Write("URL (include /api/data/v8.x): ");
var url = Console.ReadLine();
BaseUri = new Uri(url);
var absoluteUri = BaseUri.AbsoluteUri;
_authInfo.Resource = absoluteUri;
Console.Write("ClientId: ");
var clientId = Console.ReadLine();
_authInfo.ClientId = clientId;
Console.Write("RedirectUri: ");
var redirectUri = Console.ReadLine();
_authInfo.RedirectUri = new Uri(redirectUri);
var authResourceUrl = new Uri($"{_authInfo.Resource}/api/data/");
var authenticationParameters = await AuthenticationParameters.CreateFromResourceUrlAsync(authResourceUrl);
_authInfo.AuthorityUrl = authenticationParameters.Authority;
_authInfo.Resource = authenticationParameters.Resource;
_authInfo.Context = new AuthenticationContext(_authInfo.AuthorityUrl, false);
}
private static async Task RefreshAccessToken()
{
if (!IsCrmOnline())
return;
Console.WriteLine($"Acquiring token from: {_authInfo.Resource}");
AuthenticationResult authResult;
try
{
authResult = await _authInfo.Context.AcquireTokenSilentAsync(_authInfo.Resource, _authInfo.ClientId);
}
catch (AdalSilentTokenAcquisitionException astae)
{
Console.WriteLine(astae.Message);
authResult = await _authInfo.Context.AcquireTokenAsync(_authInfo.Resource, _authInfo.ClientId, _authInfo.RedirectUri, new PlatformParameters(PromptBehavior.RefreshSession));
}
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
}
Thanks to #aravind who pointed out the active-directory-dotnet-native-headless sample.
The sample contains a FileCache class which inherits from Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache. That class manages caching of the credentials to an encrypted file on disk. This means that there is only one prompt on first run and after that the credentials are locally stored.
The final pieces of the puzzle are:
Calling a different constructor signature to initialize AuthenticationContext with the FileCache:
_authInfo.Context = new AuthenticationContext(
_authInfo.AuthorityUrl, false, new FileCache());
Obtaining credentials from the user into a Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential object (see the TextualPrompt() method in the sample)
Passing the credentials to a different method signature for AcquireTokenAsync():
authResult = await _authInfo.Context.AcquireTokenAsync(
_authInfo.Resource, _authInfo.ClientId, userCredential);
If "application needs to run with no user interaction" use ClientCredential flow eg:
public static string GetAccessTokenUsingClientCredentialFlow(Credential cred) {
AuthenticationContext ac = new AuthenticationContext(cred.Authority);
AuthenticationResult r = ac.AcquireTokenAsync(cred.ResourceId, new ClientCredential(cred.ClientId, cred.ClientSecret)).Result;
return r.AccessToken;
}
Related
I am trying to create multiple VMs to multiple Subscriptions programmatically. So I need to list all subscriptions that I can access. But I cannot grant permissions to registered app, so I have to use my own Azure credential.
Then I tried
var subscriptionClient = new Microsoft.Azure.Management.ResourceManager.Fluent.SubscriptionClient(new DefaultAzureCredential());
and
var subscriptionClient = new Microsoft.Azure.Management.ResourceManager.Fluent.SubscriptionClient(new UserPasswordCredential(username,password));
but none of them compiles.
The answer of question How to list subscriptions with Microsoft.Azure.ResourceManager? is almost the answer of my question, but I cannot add comment to ask more question about it.
I installed Microsoft.IdentityModel.Clients.ActiveDirectory version 3.13.2.870 and tried:
var ctx = new AuthenticationContext("https://login.microsoftonline.com/common");
but ctx doesn't have AcquireToken, it only has AcquireTokenAsync. Unfortunately the following code still doesn't work
var mainAuthRes = await context.AcquireTokenAsync(m_resource, m_clientId, new Uri(m_redirectURI), PromptBehavior.Always);
The compiler says the fourth parameter is wrong which means
context.AcquireTokenAsync(string resource, string client , Uri uri , PromptBehavior promptBehavior )
is not a valid method.
Is there any way to list subscriptions with my current azure credential (without registering app) using C#?
Try the code works for me, it uses the VisualStudioCredential of Azure.Identity to auth, it will list all the subscriptions in all the AAD tenants that you can access(the user account logged in VS).
using Azure.Core;
using Azure.Identity;
using Microsoft.Azure.Management.ResourceManager;
using Microsoft.Rest;
using System;
using System.Threading;
namespace ConsoleApp2
{
class Program
{
public static void Main(string[] args)
{
VisualStudioCredential tokenCredential = new VisualStudioCredential();
TokenRequestContext requestContext = new TokenRequestContext(new string[] { "https://management.azure.com" });
CancellationTokenSource cts = new CancellationTokenSource();
var accessToken = tokenCredential.GetToken(requestContext, cts.Token);
ServiceClientCredentials serviceClientCredentials = new TokenCredentials(accessToken.Token);
SubscriptionClient SubscriptionClient = new SubscriptionClient(serviceClientCredentials);
var tenants = SubscriptionClient.Tenants.List();
foreach (var tenant in tenants)
{
//Console.WriteLine(tenant.TenantId);
VisualStudioCredentialOptions visualStudioCredentialOptions = new VisualStudioCredentialOptions{ TenantId = tenant.TenantId };
VisualStudioCredential tokenCredential1 = new VisualStudioCredential(visualStudioCredentialOptions);
TokenRequestContext requestContext1 = new TokenRequestContext(new string[] { "https://management.azure.com" });
CancellationTokenSource cts1 = new CancellationTokenSource();
var accessToken1 = tokenCredential1.GetToken(requestContext, cts1.Token);
ServiceClientCredentials serviceClientCredentials1 = new TokenCredentials(accessToken1.Token);
SubscriptionClient SubscriptionClient1 = new SubscriptionClient(serviceClientCredentials1);
var subs = SubscriptionClient1.Subscriptions.List();
foreach (var sub in subs)
{
//Console.WriteLine(sub.DisplayName);
Console.WriteLine($"SubscriptionName : {sub.DisplayName}");
Console.WriteLine($"SubscriptionId : {sub.SubscriptionId}");
Console.WriteLine($"TenantId : {tenant.TenantId}");
Console.WriteLine($"State : {sub.State}");
Console.WriteLine();
}
}
}
}
}
You can try using the REST API Subscriptions - List.
GET https://management.azure.com/subscriptions?api-version=2020-01-01
Request Header:
Authorization: Bearer <access token>
Update:
The simplest way to get access token is open the link Subscriptions - List and sign in with your account.
Then click "try it" at the top right of the script.
You will find the access token in Request preview.
So we have built a set of Azure Functions that are secured by ADFS (MSAL)
We have configured an App in ADFS and got it all working well with our Android client.
We now want to do some API testing so we want to programmatically generate Auth tokens to test the API's
I can't get the following code to work at all, maybe I have the tenant ID wrong, in the App config, its a GUID (42b03d0b-d7f2-403e-b764-0dbdcf0505f6), but examples say it's our domain
string userName = "-";
string password = "-";
string clientId = "ee13c922-bf4b-4f0a-ba39-ea74e1203c6e";
var credentials = new UserPasswordCredential(userName, password);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/acostaonline.onmicrosoft.com");
var result = await authenticationContext.AcquireTokenAsync("https://graph.windows.net", clientId, credentials);
UPDATE
So changed the code to be MSAL and still trying to login via username and password. Now it just times out
string authority = "https://login.microsoftonline.com/42b03d0b-d7f2-403e-b764-0dbdcf0505f6/";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication("ee13c922-bf4b-4f0a-ba39-ea74e1203c6e", authority);
var accounts = await app.GetAccountsAsync();
Microsoft.Identity.Client.AuthenticationResult result = null;
if (accounts.Any())
{
result = await app.AcquireTokenSilentAsync(scopes, accounts.FirstOrDefault());
}
else
{
try
{
var securePassword = new SecureString();
foreach (char c in "PASSWORD") // you should fetch the password keystroke
securePassword.AppendChar(c); // by keystroke
result = await app.AcquireTokenByUsernamePasswordAsync(scopes, "AUSER#acosta.com",
securePassword);
}
}
Error
SocketException: A connection attempt failed because the connected
party did not properly respond after a period of time, or established
connection failed because connected host has failed to respond
172.26.200.77:443
It seems that the code you provided is using ADAL instead of MSAL.
The main difference is that with ADAL you would use an AuthenticationContext to acquire tokens, whereas in MSAL you use ConfidentialClientApplication or PublicClientApplication, depending on if the application is running in a back-end or on the user's device.
Here is the article about Differences between ADAL.NET and MSAL.NET applications.
When you use MSAL.Net to get a token for the Microsoft Graph API, you could use the following code:
public static PublicClientApplication PublicClientApp = new
PublicClientApplication(ClientId);
var app = App.PublicClientApp;
ResultText.Text = string.Empty;
TokenInfoText.Text = string.Empty;
var accounts = await app.GetAccountsAsync();
authResult = await app.AcquireTokenSilentAsync(_scopes, accounts.FirstOrDefault());
For more details, you could refer to this article, in left menu also includes Android and iOS.
I am trying to authenticate Xamarin app using office365 user. I registered the app in Aure Active Directory using my office365 user. The below code works perfectly fine for my office365 user.
But, as soon as I use any other office365 user, I get an error. Error details mentioned below
Error Message: The client has not listed any permissions for 'AAD Graph' in the requested permissions in the client's application registration. Or, The admin has not consented in the tenant. Or, Check the application identifier in the request to ensure it matches the configured client application identifier. Please contact your admin to fix the configuration or consent on behalf of the tenant.
I am not sure why the below logic works for my Office365 user account and does not work for the rest of office365 user accounts
string authority = "https://login.windows.net/common";
string graphResourceUri = "https://graph.windows.net";
var data = await auth.Authenticate(authority, graphResourceUri, clientId, returnUri);
public async Task<AuthenticationResult> Authenticate(string authority, string resource, string clientId, string returnUri)
{
try
{
var authContext = new AuthenticationContext(authority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
var controller = UIApplication.SharedApplication.KeyWindow.RootViewController;
var uri = new Uri(returnUri);
var platformParams = new PlatformParameters(controller);
var authResult = await authContext.AcquireTokenAsync(resource, clientId, uri, platformParams);
return authResult;
}
catch (Exception ex)
{
var error = ex.Message.ToString();
return null;
}
}
For signout functionality, I used the below code.
public void ClearAllCookies(string authority)
{
var authContext = new AuthenticationContext(authority);
authContext.TokenCache.Clear();
NSHttpCookieStorage CookieStorage = NSHttpCookieStorage.SharedStorage;
foreach (var cookie in CookieStorage.Cookies)
CookieStorage.DeleteCookie(cookie);
}
I have a .NET Web API that I am using to do some interaction with Microsoft Graph and Azure AD. However, when I attempt to create an extension on the user, it comes back with Access Denied.
I know it is possible from the documentation here however, it doesnt seem to work for me.
For the API, I am using client credentials. So my web app authenticates to the API using user credentials, and then from the API to the graph it uses the client.
My app on Azure AD has the Application Permission Read and Write Directory Data set to true as it states it needs to be in the documentation for a user extension.
I know my token is valid as I can retrieve data with it.
Here is my code for retrieving it:
private const string _createApprovalUrl = "https://graph.microsoft.com/beta/users/{0}/extensions";
public static async Task<bool> CreateApprovalSystemSchema(string userId)
{
using(var client = new HttpClient())
{
using(var req = new HttpRequestMessage(HttpMethod.Post, _createApprovalUrl))
{
var token = await GetToken();
req.Headers.Add("Authorization", string.Format("Bearer {0}", token));
req.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var requestContent = JsonConvert.SerializeObject(new { extensionName = "<name>", id = "<id>", approvalLimit = "0" });
req.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
using(var response = await client.SendAsync(req))
{
var content = await response.Content.ReadAsStringAsync();
ApprovalSystemSchema schema = JsonConvert.DeserializeObject<ApprovalSystemSchema>(content);
if(schema.Id == null)
{
return false;
}
return true;
}
}
}
}
Is there anyone who may have a workaround on this, or information as to when this will be doable?
Thanks,
We took a look and it looks like you have a bug/line of code missing. You appear to be making this exact request:
POST https://graph.microsoft.com/beta/users/{0}/extensions
Looks like you are missing the code to replace the {0} with an actual user id. Please make the fix and let us know if you are now able to create an extension on the user.
I am trying to use the following overload :
authContext.AcquireTokenAsync(, )
This works fine with a simple console app and I am able to retrieve the token.
But when I run this from a web application, the call doesn't return and no exception is thrown. I checked in fiddler and it seems the connection closes on this call.
How to get this resolved? Is it related to HttpContext having restricted permissions?
Its probably because of the async/await, for my code I just added await before the graphClient.Users[FromUserEmail].SendMail(message, true).
This works:
var sendmail = graphClient.Users[FromUserEmail].SendMail(message, true);
Task.Run(async () =>
{
try
{
await sendmail.Request().PostAsync();
}
catch (Exception ex)
{
throw ex;
}
}).Wait();
Normally, we acquire the token using authorization code grant flow in a web application. To achieve the goal, we need to implement OnAuthorizationCodeReceived event like below(full code sample):
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
// If you create the redirectUri this way, it will contain a trailing slash.
// Make sure you've registered the same exact Uri in the Azure Portal (including the slash).
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
}
If you want to implement the client credentials flow, you can refer code below:
string authority = "https://login.microsoftonline.com/{tenantId}";
string clientId = "{clientId}";
string secret = "{secret}";
string resource = "https://graph.windows.net";
var credential = new ClientCredential(clientId, secret);
AuthenticationContext authContext = new AuthenticationContext(authority);
var token = authContext.AcquireTokenAsync(resource, credential).Result.AccessToken;
If you still have the problem, it is helpful to share the detail code.
it's just a common mistakes that developper makes regarding asyn/await
you have just to add async Task<> to your method and of course the await keyword near to the call of methode that retreive the token