How to acquire a user based token from Azure Graph API - azure

I have an Azure Active Directory and in my Web Api I have a piece of code that I can get a token from Azure Graph Api using the Application that I have registered with Azure and a Client Certificate.
Here is the code that I use right now:
public static string AcquireServiceToken()
{
var authority = string.Format(_authority, "common");
var authContext = new AuthenticationContext(authority);
var result = authContext.AcquireToken(_serviceTokenResourceId, new ClientAssertionCertificate(_serviceTokenClientId, GetClientCertificate(_certThumbprint)));
return result.AccessToken;
}
This snippet of code works just fine, now what I need is a more specific token which has logged-in user's context, so basically I need to be able to pass in a username and password and get a Graph token back from Azure.
Any Ideas?

A web API can access another web API (in this case, the Graph) as the current user by obtaining a new token via the onbehalfof flow. See https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof for an example. The direct use of username and password is not recommended, credentials should never be collected outside of Azure AD own pages, and in this specific case it would not work (or will stop working soon)

AcquireToken has another overload that takes in a UserCredential object and U assume you could use that (You will need the TenantId of the Active Directory that the users you need to acquire the tokens for)
Your function will look something like this: (Please fill in the _variables with your own application information)
public static string AcquireTokenWithoutUserCredentials(string userName, string password)
{
var authContext = new AuthenticationContext(string.Format(_authority, _userTokenTenantId));
var userCreds = new UserCredential(userName, password);
var result = authContext.AcquireToken(_resourceId, _userTokenClientId, userCreds);
return result.AccessToken;
}
Looking at your code, looks like you have a multi tenant scenario, in which you use "common" as TenatName, which I'm not sure how/if it will work using the code I pasted here but give it a try ...

Related

How to login to a Azure Active Directory as a User in Test-Code?

I'm new to Azure and struggle a little in learning all the functionalities of the Azure Active Directory (AAD), so I hope you can clear some things up for me. Here is what I already did:
I registered a web app which serves as a resource provider and offers different APIs behind a API management service.
The web app has several users and roles in the AAD. Plus, more detailed permissions are set on App-Level. So the AAD doesn't control all permissions of my users.
Users are authenticated by using OAuth 2.0. In practice, this means if a new user tries to login to my app he gets redirected to Microsofts login page, enters username and password and then gets a JWT token from Microsofts authentication server.
Now what I want to do:
I want to write an app running on my build server which tests the user permissions. The app has to be written in C# .NET Core. Now I'm struggling on how to log in as a user from my code, so my question is:
How can i log in as a user from code to AAD and get the JWT token to test the user permissions? Can I do this by just using username / password, or do I need to register my test app in the AAD? What are the best solutions to reach my goals?
Thank you in advance
Juunas' comment already covered most of what is required. Just putting a bit more detail behind it.
You can use MSAL (link) to write a .NET Core application that accesses your API.
Within MSAL, you need to use username password authentication (Resource Owner Password Credentials grant) to acquire a JWT token. Please never use this grant outside your testing application.
Depending on how your app is configured, using just the clientId of the API could be enough. It would however be best practice to register a separate native app.
Some wording to help you along:
ClientId: The id of the client application which is requesting the token.
Scope: The scope of the API you acquire the token for. Should already be configured somewhere in your API. Usually something with the AppId URI. Possible examples could look like:
https://<yourtenant>.onmicrosoft.com/<yourapi>/user_impersonation
https://<clientId-of-API>/.default
...
Authority: Your AAD, e.g. https://login.microsoftonline.com/yourtenant.onmicrosoft.com
Code example for the password grant from the wiki (more examples there):
static async Task GetATokenForGraph()
{
string authority = "https://login.microsoftonline.com/contoso.com";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication(clientId, authority);
try
{
var securePassword = new SecureString();
foreach (char c in "dummy") // you should fetch the password
securePassword.AppendChar(c); // keystroke by keystroke
result = await app.AcquireTokenByUsernamePasswordAsync(scopes, "joe#contoso.com",
securePassword);
}
catch(MsalException)
{
// See details below
}
Console.WriteLine(result.Account.Username);
}
I actually find out a way to do it in "pure" C# without using the MSAL library, which I had some trouble with. So if you're looking for a solution w/o MSAL, you can do it the way described below.
Prerequisites
A user must exist in the AAD and must not use a Microsoft Account (source in Active Directory must not be "Microsoft Account").
A client application must be registered in the Azure Active Directory. The client app must be granted permissions to the app you want to test. If the client app is of type "Native", no client secret must be provided. If the client app is of type "Web app / api", a client secret must be provided. For testing purposes, its recommended to use an app of type "Native" without a client secret.
There must be no two factor authentication.
C# Code
You can than create a class "JwtFetcher" and use code like this:
public JwtFetcher(string tenantId, string clientId, string resource)
{
this.tenantId = !string.IsNullOrEmpty(tenantId) ? tenantId : throw new ArgumentNullException(nameof(tenantId));
this.clientId = !string.IsNullOrEmpty(clientId) ? clientId : throw new ArgumentNullException(nameof(clientId));
this.resource = !string.IsNullOrEmpty(resource) ? resource : throw new ArgumentNullException(nameof(resource));
}
public async Task<string> GetAccessTokenAsync(string username, string password)
{
var requestContent = this.GetRequestContent(username, password);
var client = new HttpClient
{
BaseAddress = new Uri(ApplicationConstant.Endpoint.BaseUrl)
};
var message = await client.PostAsync(this.tenantId + "/oauth2/token", requestContent).ConfigureAwait(false);
message.EnsureSuccessStatusCode();
var jsonResult = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
dynamic objectResult = JsonConvert.DeserializeObject(jsonResult);
return objectResult.access_token.Value;
}
private FormUrlEncodedContent GetRequestContent(string username, string password)
{
List<KeyValuePair<string, string>> requestParameters = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.GrantType, ApplicationConstant.RequestParameterValue.GrantTypePassword),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.Username, username),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.Password, password),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.ClientId, this.clientId),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.Resource, this.resource)
};
var httpContent = new FormUrlEncodedContent(requestParameters);
return httpContent;
}
The grant type for this is just "password".

authority_not_in_valid_list: 'authority' is not in the list of valid addresses

I am trying to call a Authenticated API from my client app. However, when making AcquireTokenAsync, I get following error "authority_not_in_valid_list: 'authority' is not in the list of valid addresses"
here is my code snippet:
resourceUrl = "https://myApiEndPoint.com";
var clientCredential =
new ClientCredential( myClientAppId, myClientSecretKey );
// myClientAppId and myClientSecretKey are the values from Azure Portal
var authContext =
new AuthenticationContext( "https://my_authority/myApiEndPoint");
return await authContext.AcquireTokenAsync( resourceUrl, clientCredential );
In my azure Portal for my client Id of app, I have granted delegated permission to access https://myApiEndPOint.com api.
Any thoughts on what could be causing this issue and what does it mean by not in valid list?
I understand that:
you created your application in the Azure portal, and therefore the authority is the Azure AD endpoint. Therefore the authority is probably https://login.microsoftonline.com/common? Or do you have good reasons to use "https://my_authority" ?
you have granted delegated permissions to access the API. This means that your application will access the API in the name of the user. However the AcquireTokenAsync method that you use is using the "ClientCredential" flow (meaning with an application secret)
You probably rather want to use another override passing the resourceUri, the clientId, ...
If this is your use case, I suggest you have a look to the active-directory-dotnet-webapi-onbehalfof sample (See here)

How to silently get access token to user subscription Azure Batch?

i am working on project, where we have service that run computation on Azure Batch in user subscription mode (because we are using custom image). I have now my code fully working, but it requires every launch to provide user credentials to log into Azure Active Directory app before it can create Batch pools and so on. Because it will run as background service, i need to log in silently with some provided user without popup asking user to log in.
I have registered native app in Azure and set its access to Azure Batch service, created Azure AD user, and got all ids and names from it.
Here is my code i am using now.
private const string AuthorityUri = "https://login.microsoftonline.com/common";
private const string BatchResourceUri = "https://batch.core.windows.net";
private const string BatchAccountEndpoint = "https://<BATCH SERVICE NAME>.westeurope.batch.azure.com";
private const string ClientId = "<AZURE APP GUID ID>";
...
public static async Task<string> GetAuthenticationTokenAsync()
{
var authContext = new AuthenticationContext(AuthorityUri);
//here it will throw exception about no token found in cache and to call AquireToken
var authResult = await authContext.AcquireTokenSilentAsync(BatchResourceUri, ClientId, new UserIdentifier("<AD USER GUID ID>", UserIdentifierType.UniqueId));
//this works fine, but show popup dialog for login
/*var authResult = await authContext.AcquireTokenAsync(BatchResourceUri,
ClientId,
new Uri(RedirectUri),
new PlatformParameters(PromptBehavior.Auto));*/
return authResult.AccessToken;
}
...
Func<Task<string>> tokenProvider = () => GetAuthenticationTokenAsync();
using (BatchClient batchClient = await BatchClient.OpenAsync(new BatchTokenCredentials(BatchAccountEndpoint, tokenProvider)))
{
...
}
Classic way with AquireToken with popup for login is working fine. I have tried to use AquireTokenSilent (as is shown in code), but i am getting error about no token cache and need to call AquireToken.
Id used in UserIdentifier is user id guid taken from Azure Active Directory user blade.
Does anybody know, how to update my code so i will be able to silently log into Azure Batch with specified user and is this even possible?
Thanks for help.
AcquireTokenSilent is not meant for this use case. It will try to get the token from the cache where it was previously stored by AcquireTokenAsync.
And AcquireTokenAsync will pop up a login dialog, so you can't use that in your batch app either.
Take a look at either authenticating with a certificate or with username/password.
In the first sample, you need to create a ClientAssertionCertificate with
certCred = new ClientAssertionCertificate(clientId, cert);
this is then used for AcquireTokenAsync:
result = await authContext.AcquireTokenAsync(todoListResourceId, certCred);
The other sample creates a UserPasswordCredential with
var uc = new UserPasswordCredential(user, password);
and then also uses it with AcquireTokenAsync in a slightly different way:
authContext.AcquireTokenAsync(todoListResourceId, clientId, uc);
There are some limitations as to what you can do with the tokens that are based on the two different authentication methods. For example, using the access token for EWS Impersonation requires using the certificate method.

Simple Directory Lookup in Azure Active Directory

I am writing a simple desktop application that needs to retrieve some basic properties about a user from Microsoft’ directory. Specifically:
I am writing a single tenant native LOB application.
The application runs on my desktop.
The application runs as my logged on domain account.
The organization' domain accounts are synced to AAD.
I am not trying to secure a native web app or a Web API or anything like that. I do not need users to sign in.
I have email addresses of folks in my organization from an external event management tool. I need to lookup the AAD account profile data (address book info - specifically job title) from AAD based on the email address. I will only be reading AAD data.
So far, I have done the following:-
It appears that the Azure AD Graph API is the right way to fetch the profile information. In particular, the information is available at the endpoint: https://graph.windows.net/{tenant}/users/{email}?api-version=1.6
When registering the native application in AAD, no key was provided. So I don't have a client secret.
Looked at the sample in GitHub here: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-console. The instructions here seem to be wrong because no Keys section is available [see (2)].
Based on the sample above, I wrote a simple function. Code is below:
private static async Task PrintAADUserData(string email)
{
string clientId = "0a202b2c-6220-438d-9501-036d4e05037f";
Uri redirectUri = new Uri("http://localhost:4000");
string resource = "https://graph.windows.net/{tenant}";
string authority = "https://login.microsoftonline.com/{tenant}/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
string api = String.Format("https://graph.windows.net/{tenant}/users/{0}?api-version=1.6", email);
LOG.DebugFormat("Using API URL {0}", api);
// Create an HTTP client and add the token to the Authorization header
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.AccessTokenType, authResult.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(api);
string data = await response.Content.ReadAsStringAsync();
LOG.Debug(data);
}
Questions
The application when run was able to bring up the authentication page. Why do I need that? The application already runs as my domain account. Is an additional authentication necessary? If I were to run this application in Azure as a worker process, then I would not want to use my domain credentials.
The primary problem seems to be the resource URL which is wrong. What resource do I need to specify to access the Azure AD Graph API?
Thanks,
Vijai.
EDITS
Based on the comments from #Saca, the code and application has been edited.
Code
string clientId = ConfigurationManager.AppSettings["AADClientId"];
string clientSecret = ConfigurationManager.AppSettings["AADClientSecret"];
string appIdUri = ConfigurationManager.AppSettings["AADAppIdURI"];
string authEndpoint = ConfigurationManager.AppSettings["AADGraphAuthority"];
string graphEndpoint = ConfigurationManager.AppSettings["AADGraphEndpoint"];
AuthenticationContext authContext = new AuthenticationContext(authEndpoint, false);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.windows.net", new ClientCredential(clientId, clientSecret));
ExistingTokenWrapper wrapper = new ExistingTokenWrapper(authResult.AccessToken);
ActiveDirectoryClient client = new ActiveDirectoryClient(new Uri(graphEndpoint), async () => await wrapper.GetToken());
IUser user = client.Users.Where(_ => _.UserPrincipalName.Equals(email.ToLowerInvariant())).Take(1).ExecuteSingleAsync().Result;
App
Error
Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> Microsoft.Data.OData.ODataErrorException: Insufficient privileges to complete the operation. ---> System.Data.Services.Client.DataServiceQueryException: An error occurred while processing this request. ---> System.Data.Services.Client.DataServiceClientException: {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
It appears that despite giving the right permissions, the correct resource and being able to acquire a token, there is still something missing.
The key thing to consider here is if your application will be a headless client run from a secure server or desktop client run by users on their machines.
If the former, then your application is considered a confidential client and can be trusted with secrets, i.e. the keys. If this is your scenario, which is the scenario covered by the sample, then you need to use clientId and clientSecret.
The most likely reason you are not seeing a Keys section in the your application's Configure page is that, instead of selecting Web Application and/or Web API as per step #7 in the sample, you selected Native Client Application when first creating the application. This "type" can't be changed, so you'll need to create a new application.
If your scenario is the latter, then your application is considered a public client and can't be trusted with secrets, in which case, your only options is to prompt the user for credentials. Otherwise, even if your app has it's own authorization layer, it can easily be decompiled and the secret extracted and used.
Your resource URL is correct by the way.
Turns out the real issue was not with the code. I am not an AAD administrator. It appears that any application needing to perform authentication against AAD in our tenant needs to have permissions enabled by the AAD administrators. Once they enabled permissions for my application (and took ownership of the AAD registration as well), this started working.
Hope help some one that are using GraphClient:
var userPriNam = "johndoe#cloudalloc.com";
var userLookupTask = activeDirectoryClient.Users.Where(
user => user.UserPrincipalName.Equals(userPriNam, StringComparison.CurrentCultureIgnoreCase)).ExecuteSingleAsync();
User userJohnDoe = (User)await userLookupTask;
from https://www.simple-talk.com/cloud/security-and-compliance/azure-active-directory-part-5-graph-api/

Getting username and group info from Azure using adal4j

I am developing a mobile app in which I need to authenticate a user against Azure AD. Basically the user will be prompted their organisational email and password, which the mobile phone app sends to the backend server which will authenticate.
I have the 'public-client-app-sample' of 'azure-activedirectory-library-for-java' working, and can authenticate against 'graph.windows.net':
private static AuthenticationResult getAccessTokenFromUserCredentials(
String username, String password) throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", CLIENT_ID, username, password,
null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}
However, this does not return any userInfo (is null), and I can't figure out at this moment how to query to get a list with groups the user belongs to?
Do I just do manual lookups using the API using the tokens obtained from Adal4j, or is there a provided function within the library?
I am only starting with Azure, so it might be obvious, I might just be looking in the wrong places. I tried e.g. 'https://graph.windows.net/xxx.com/groups?api-version=1.5' but get 'Resource 'https://graph.windows.net/xxx.com/groups?api-version=1.5' is not registered for the account.'
First, you're absolutely right, adal4j was failing to return UserInfo. The reason for this was that the token request did not include the scope=openid parameter, which is required if the caller wants an id_token in the response. I opened an issue, and it has already been resolved. So, an updated copy of adal4j will fix your first issue of not getting UserInfo.
Now, regarding group membership for the current user: Normally, I would recommend that you simply configure you application to return the groups claim. This can be done very easily by changing the application manifest (downloaded and uploaded via the Azure portal, under the Application's configuration page) to include:
"groupMembershipClaims": "All",
Unfortunately, adal4j does not yet include the groups claim in the result of getUserInfo(), so that probably won't work much for you (issue opened, it really depends on how quickly it gets implemented, or if you want to implement youself).
Regardless, because it is possible for there to be too many groups to include in the token (indicated by , your application should always be able to use the AAD Graph API to retrieve the full set of groups the user is a member of.
And that brings me to the last point: adal4j does not implement a client of the Azure AD Graph API. So yes, you would have to implement that yourself (or perhaps use/modify an existing OData client).
(You can read more about Group Claims in this Azure AD blog post.)

Resources