I am creating a new role in azure application using Azure AD Graph API. what i'm doing is getting access token from azure using this code:
ClientCredential clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceID, clientCredential);
return authenticationResult.AccessToken;
And Creating Role using following code:
//Fetch application Data from azure AD
IApplication application = await activeDirectoryClient.Applications.GetByObjectId(RoleModel.ApplicationID).ExecuteAsync();
AppRole NewRole = new AppRole
{
Id = CurrentRoleID,
IsEnabled = true,
AllowedMemberTypes = new List<string> { "User" },
Description = RoleModel.RoleDescription,
DisplayName = RoleModel.RoleName,
Value = RoleModel.RoleName
};
application.AppRoles.Add(NewRole as AppRole);
await application.UpdateAsync();
I also granted All Application Permissions not the Delegated Permissions from Azure portal to Microsoft Graph API. But i'm getting this error:
{"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."},"requestId":"e4187318-4b72-49fb-903d-42d419b65778","date":"2019-02-21T13:45:23"}}
Note:
I'm able to create new user and updated a user using this access token though.
For Testing:
For testing purpose, I granted Delegated Permissions to application and use client credential flow to get access token of current logged-in user and if the signed in user had enough directory role he/she can created role in application this is working fine.
Question:
So, is it possible to create a new role in application using application credential flow? if so, am i missing something?
Updated:
Added all Application Permission for API Windows Azure Active Directory and Grant admin consent.
Access Token:
Access Token returned from ADzure AD
Question: So, is it possible to create a new role in application using
application credential flow? if so, am i missing something?
Answer to your general question is Yes, you can add a new role to application's roles using Azure AD Graph API and client credentials flow.
Working Code
Given below is the working code (it's a quick and dirty console application, just to make sure I test it before confirming)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace AddAzureADApplicationRoles
{
class Program
{
static void Main(string[] args)
{
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(new Uri("https://graph.windows.net/{myTenantId}"),
async () => await GetTokenForApplication());
//Fetch application Data from azure AD
IApplication application = activeDirectoryClient.Applications.GetByObjectId("{MyAppObjectId}").ExecuteAsync().GetAwaiter().GetResult();
AppRole NewRole = new AppRole
{
Id = Guid.NewGuid(),
IsEnabled = true,
AllowedMemberTypes = new List<string> {"User"},
Description = "My Role Description..",
DisplayName = "My Custom Role",
Value = "MyCustomRole"
};
application.AppRoles.Add(NewRole as AppRole);
application.UpdateAsync().GetAwaiter().GetResult();
}
public static async Task<string> GetTokenForApplication()
{
string TokenForApplication = "";
AuthenticationContext authenticationContext = new AuthenticationContext(
"https://login.microsoftonline.com/{MyTenantId}",
false);
// Configuration for OAuth client credentials
ClientCredential clientCred = new ClientCredential("{AppId}",
"{AppSecret}"
);
AuthenticationResult authenticationResult =
await authenticationContext.AcquireTokenAsync("https://graph.windows.net", clientCred);
TokenForApplication = authenticationResult.AccessToken;
return TokenForApplication;
}
}
}
Probable Issue behind your specific exception
I think you have given application permissions on Microsoft Graph API, instead of permissions required for Azure AD Graph API.
While setting required permissions for your application, in Select an API dialog, make sure you select "Windows Azure Active Directory" and not "Microsoft Graph". I am giving screenshot for more detail next.
Steps to give required permissions
Notice that my app doesn't require any permissions on "Microsoft Graph API". It only has application permissions given for "Windows Azure Active Directory".
So, choose the appropriate application permission for your requirement, and make sure you do "Grant Permissions" at the end to provide Admin consent, as all the application permissions here mention Requires Admin as Yes.
On a side note, when you first create an app registration, it already has one delegated permission on Windows Azure Active Directory, so you may not need to explicitly select Windows Azure Active Directory again (unless you've removed it for your app), but just select the correct application permissions and do Grant Permissions as an administrator.
Related
I am trying to get the groups in my azure tenant using my client secret and client id. Problem is I don't know what scope to pass when getting an access token to use the graph api. I used https://graph.microsoft.com/.default to get an access token but this doesn't include permission to pull groups. What's the appropriate scope to use
https://learn.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0#permissions
Permissions
One of the following permissions is required to call this API. To learn more, including how to choose permissions, see Permissions.
Permission type Permissions (from least to most privileged)
Delegated (work or school account) Group.Read.All, Group.ReadWrite.All
Application Group.Read.All, Group.ReadWrite.All
You need to configure the API access within AAD, not with the scope. Make sure that you don't forget to click on "grant permissions".
Example assumes that you require application permission. Delegated permission works similarly.
Sample code for getting the data using MSAL for authentication:
IConfidentialClientApplication app = new ConfidentialClientApplication(
"clientId",
"https://login.microsoftonline.com/yourtenant.onmicrosoft.com",
"http://localhost (redirecturi)",
new ClientCredential("secret"),
new TokenCache(), new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
try
{
AuthenticationResult result = await app.AcquireTokenForClientAsync(scopes);
System.Console.WriteLine(result.AccessToken);
using (var http = new HttpClient())
{
http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
var groupResponse = await http.GetAsync("https://graph.microsoft.com/v1.0/groups");
var groupJson = await groupResponse.Content.ReadAsStringAsync();
System.Console.WriteLine(groupJson);
}
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
}
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".
We have setup our application using Azure AD B2C and OAuth, this works fine, however I am trying to authenticate as a service in order to make service to service calls. I am slightly new to this, but I have followed some courses on Pluralsight on how to do this on "normal" Azure Active Directory and I can get it to work, but following the same principles with B2C does not work.
I have this quick console app:
class Program
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"]; //APIClient ApplicationId
private static string appKey = ConfigurationManager.AppSettings["ida:appKey"]; //APIClient Secret
private static string aadInstance = ConfigurationManager.AppSettings["ida:aadInstance"]; //https://login.microsoftonline.com/{0}
private static string tenant = ConfigurationManager.AppSettings["ida:tenant"]; //B2C Tenant
private static string serviceResourceId = ConfigurationManager.AppSettings["ida:serviceResourceID"]; //APP Id URI For API
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static AuthenticationContext authContext = new AuthenticationContext(authority);
private static ClientCredential clientCredential = new ClientCredential(clientId, appKey);
static void Main(string[] args)
{
AuthenticationResult result = authContext.AcquireToken(serviceResourceId, clientCredential);
Console.WriteLine("Authenticated succesfully.. making HTTPS call..");
string serviceBaseAddress = "https://localhost:44300/";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = httpClient.GetAsync(serviceBaseAddress + "api/location?cityName=dc").Result;
if (response.IsSuccessStatusCode)
{
string r = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(r);
}
}
}
And the service is secured like this:
private void ConfigureAuth(IAppBuilder app)
{
var azureADBearerAuthOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(azureADBearerAuthOptions);
}
In my B2C tenant I have two different applications that are pretty much setup as this:
Both applications have been setup with secrets coming from the "keys" option. The keys generated are slightly differently structured than when using Azure Active Directory.
I can successfully get a token, but I get 401 when trying to connect to the other service. Do I have to do something different on the authorization side when using B2C compared to Azure Active Directory?
Azure Active Directory B2C can issue access tokens for access by a web or native app to an API app if:
Both of these apps are registered with B2C; and
The access token is issued as result of an interactive user flow (i.e. the authorization code or implicit flows).
Currently, your specific scenario -- where you are needing an access token to be issued for access by a daemon or server app to the API app (i.e. the client credentials flow) -- isn't supported, however you can register both of these apps through the “App Registrations” blade for the B2C tenant.
You can upvote support for the client credentials flow by B2C at:
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/18529918-aadb2c-support-oauth-2-0-client-credential-flow
If the API app is to receive tokens from both the web/native app as well as the daemon/server app, then you will have to configure the API app to validate tokens from two token issuers: one being B2C and other being the Azure AD directory in your B2C tenant.
I found the following very clear article from Microsoft which explains how to set up a "service account" / application which has management access to a B2C tenant. For me, that was the use case for which I wanted to "Authenticating as a Service with Azure AD B2C".
It is possible that having management access to a B2C tenant doesn't allow you access a protected resource for which your B2C tenant is the Authorization server (I haven't tried that), so the OP's use case may be slightly different but it feels very close.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
For automated, continuous tasks, you should use some type of service account that you provide with the necessary privileges to perform management tasks. In Azure AD, you can do this by registering an application and authenticating to Azure AD. This is done by using an Application ID that uses the OAuth 2.0 client credentials grant. In this case, the application acts as itself, not as a user, to call the Graph API.
In this article, we'll discuss how to perform the automated-use case. To demonstrate, we'll build a .NET 4.5 B2CGraphClient that performs user create, read, update, and delete (CRUD) operations. The client will have a Windows command-line interface (CLI) that allows you to invoke various methods. However, the code is written to behave in a noninteractive, automated fashion.
I created an ASP.NET MVC application and configured authentication with Azure AD using OpenIDConnect. I created a user in one Azure AD and added the same in another Azure AD with right privilege.
I store the claims returned after the Azure AD authentication, in ADAL cache. I use this claim(token cache)to call various Azure Service Management API.
ClientCredential credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientID"],
ConfigurationManager.AppSettings["ida:Password"]);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
AuthenticationContext authContext = new AuthenticationContext(
string.Format(ConfigurationManager.AppSettings["ida:Authority"], organizationId), new ADALTokenCache(signedInUserUniqueName));
AuthenticationResult result = authContext.AcquireTokenSilent(ConfigurationManager.AppSettings["ida:AzureResourceManagerIdentifier"], credential,
new UserIdentifier(signedInUserUniqueName, UserIdentifierType.RequiredDisplayableId));
var token= result.AccessToken;
I have configured my application to support multitenant by adding the following in my Account/SignIn controller/action.
public void SignIn(string directoryName = "common")
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Environment.Add("Authority", string.Format(ConfigurationManager.AppSettings["ida:Authority"] + "OAuth2/Authorize", directoryName));
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Now, upon successful signin, the claims that are returned, belong to the original Azure AD in which the user is initially registered in. Thus, the claims used to call management api for any other Azure AD, in which the user is also added, does not work and throws exception as "Acquire Token failed to obtain token".
I added the name of the other Azure AD to the variable "directoryName" on runtime. This time the claims obtained worked for both the Azure AD.
How to get the SSO for multitenant application, without explicitly mentioning the Azure AD name while signing-in, which will provide me with the claims that can work for all the Azure AD in which the user is registered.
Kindly suggest.
Thanks in advance,
Rahul
I am not sure what your parameter signedInUserUniqueName is, I often write like this to get accesstoken:
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.windows.net/" + Properties.Settings.Default.TenantID);
ClientCredential credential = new ClientCredential(clientId: Properties.Settings.Default.ClientID, clientSecret: Properties.Settings.Default.ClientSecretKey);
AuthenticationResult result = authenticationContext.AcquireToken(resource: "https://management.core.windows.net/", clientCredential: credential);
var token = result.AccessToken;
I'm trying for the first time to get access to 365 using oauth2, for my Native Application.
I have registered my application in Azure AD.
The documentation says, "...In the Azure Management Portal, select your application and choose Configure in the top menu. Scroll down to keys."
But in (my) Azure Application, Configure properties, I only have Name, Client ID, URL and Logo, and the Permissions area - No "Keys" area.
Am I missing something?
Web Application And/OR Web API
As tou are looking for KEYS , You need to create your application in AD as Web Application or web API
then you can find the Key and secret.
Native Client Application
If you're developing a native client app, you don't need the keys because this auth flow doesn't require them.
So first of all you need to use ADAL (Active Directory Authentication Library) use the right version for your client program.
Then you should to reference your AD configuration for the App, note there are no KEYs required.
// Native client configuration in AzureAD
private string clientID = "3dfre985df0-b45640-443-a8e5-f4bd23e9d39f368";
private Uri redirectUri = new Uri("http://myUrl.webapi.client");
Then prepare AD authority URL and create the Auth Context.
private const string authority = "https://login.windows.net/cloudalloc.com";
private AuthenticationContext authContext = new AuthenticationContext(authority);
That's all, after that you need to ask for access tokens depending on the resource you want to access.
AuthenticationResult result = null;
try
{
result = authContext.AcquireToken(resource, clientId, redirectUri, PromptBehavior.Never);
}
catch (AdalException ex)
{
if (ex.ErrorCode != "user_interaction_required")
{
// An unexpected error occurred.
MessageBox.Show(ex.Message);
}
}
the resource could be a webapi or office 365 resource URI