Authenticate guest user in Azure AD using graph api - azure

I am trying to authenticate users in my web application using Azure AD to store user records. For authenticating the user I am using ADAL4J API (https://github.com/AzureAD/azure-activedirectory-library-for-java). I am using the the AuthenticationContext.acquireToken() method to acquire the token for users. This is working for local users in my directory but not for guest users invited to the directory.
While authenticating guest users I am getting an error : "To sign into this application the account must be added to the directory" . However, I am sure the user has been successfully added to the directory as seen through the Azure Portal. Also, I have verified the same using the graph API where I can see the guest users in the user list in the directory.
So the question is how do I authenticate the guest user in my web application through code (not through redirecting to the Azure UI)?
EDIT :
This the method to which I am passing the username and password of the user:
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("https://login.windows.net/<tenant_name>", false, service);
Future<AuthenticationResult> future = context.acquireToken(
"https://graph.windows.net", CLIENT_ID, username, password,
null);
result = future.get();
} catch(Exception e){
e.printStackTrace();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException(
"authentication result was null");
}
return result;
}

With the information you provided, I feel like the issue here is related to the login endpoint. Remember that the common endpoint uses the logged in user to help 'guess' which tenant endpoint to authenticate to. If you are doing more tricky things like guest accounts, it is very likely the common endpoint will not figure out all the right details.
I recommend you specifically call your tenant's login endpoint, through the whole process, and see if that resolves your issues.
Let me know if this helps!

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".

Can't access the calendars in office365 groups

I got Unauthorized when calling to get Calendar in the Microsoft graph API, I use this verification to retrieve an access token.
I think the problem is that when I want to retrieve the Get HTTP request on Calendar the access token isn't created with credentials which result in the Unauthorized call. How can I change my code to be able to send Credentials with it and get a better Token with more privileges?
In the Azure portal, I granted all permissions to see whether that the application permissions weren't set correctly.
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
ClientCredential clientCred = new ClientCredential(_azureAd_ClientId, _azureAd_SecretKey);
string tokenResult;
AuthenticationResult authenticationResult;
try
{
authenticationResult = await authenticationContext.AcquireTokenAsync(_azureAd_GraphResource, clientCred);
var user = authenticationResult.UserInfo;
string_token = authenticationResult.AccessToken;
tokenResult = JsonConvert.SerializeObject(authenticationResult);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex.InnerException);
}
Reading through the comments it seems that you may need to trigger a new consent prompt in order to grant the application the permissions you have re-configured. If this is a V1 app, you can do so by adding prompt=admin_consent to the end of your request. If this is a V2 app, you will want to use the /adminconsent endpoint. More info on that is here.
There is also some more documentation on admin consent in general here.

B2B users cannot sign in to Tenant using v2.0 endpoint & MSAL Auth flow

I am trying to create a B2B Management portal. I've started off with this sample since it uses MSAL and Graph API.
user#live.se is in the tenant. It's been invited as a "guest user", i.e a B2B user. However, signing in with user#live.se does not work even though it's been added to the tenant. Following error after sign-in:
AADSTS50020: User account 'user#live.se' from external identity provider 'live.com' is not supported for api version '2.0'. Microsoft account pass-thru users and guests are not supported by the tenant-independent endpoint. Trace ID: 2ad8bee0-d00a-4896-9907-b5271a113300 Correlation ID: 0ea84617-4aa1-4830-859f-6f418252765e Timestamp: 2017-10-03 15:35:22Z
I changed the authority (from common) to only allow users from my tenant (requirement):
https://login.microsoftonline.com/tenant.onmicrosoft.com/v2.0
Do guests not count as part of my tenant when using MSAL? that would mean I have to use "old" tech, i.e ADAL and AAD Graph, which is not recommended, and feels kinda lame.
If you pass the specific tenant value in the authority, then
Only users with a work or school account from a specific Azure AD tenant can sign in to the application. Either the friendly domain name of the Azure AD tenant or the tenant's GUID identifier can be used.
That's means the Microsoft Account is not supported in this scenario. Refer here for the Microsoft Account and Work or school accounts. And in this scenario, if you new a user user from other tenant, it should also works.
You can refer the document for tenant from link below:
Fetch the OpenID Connect metadata document
I know this is an old thread but just in case anyone stumbles upon it, here is a solution:
In cases of Personal guest accounts, use Credential Grant Flow (Get access without a user).
To do that, you would first need to grant appropriate permission (of Type Application) for the API you wanted to use on behalf of the signing user. This would let you acquire access token with the application's identity itself rather than the signed in user.
Next get token like this (in this sample, I'm getting access token for Graph API):
public async Task<string> GetAccessToken()
{
using (HttpClient httpClient = new HttpClient())
{
string token = "";
try
{
httpClient.BaseAddress = new Uri($"https://login.microsoftonline.com/{tenantId}");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
HttpRequestMessage request = new HttpRequestMessage();
List<KeyValuePair<string, string>> body = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("scope", "https://graph.microsoft.com/.default"),
new KeyValuePair<string, string>("client_secret", appSecret),
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
request.Method = HttpMethod.Post;
request.RequestUri = new Uri($"{httpClient.BaseAddress}/oauth2/v2.0/token");
request.Content = new FormUrlEncodedContent(body);
var response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsAsync<dynamic>();
token = content.access_token;
}
catch (Exception e)
{
}
return token;
}
}
Tip: If your goal is also Graph API, don't try to get logged in user info by using the /me endpoint in this case. Since the token was generated using the application identity rather than the signed in user, /me would be the application not the logged in user. What you want to do is: retrieve logged in user id from the Claim (Type: http://schemas.microsoft.com/identity/claims/objectidentifier) and use the /user/{userid} endpoint.
I found: for personal accounts (Get access without a user) in the body of the request you must to use grant_type = 'client_credentials' and for corporate accounts to use grant_type = 'authorization_code'

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.

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