Configuring ASP.Net application to support muliti tenant using Azure AD account - azure

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;

Related

Get a Power BI access token after logged into Azure AD

I have code that logs me into Azure AD, but I cant' figure out how to get the access token to call the REST API's or PowerBI
There is the sample code to get access token for Power BI REST API.
//The client id that Azure AD created when you registered your client app.
string clientID = "{Client_ID}";
//RedirectUri you used when you register your app.
//For a client app, a redirect uri gives Azure AD more details on the application that it will authenticate.
// You can use this redirect uri for your client app
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
//Resource Uri for Power BI API
string resourceUri = "https://analysis.windows.net/powerbi/api";
//OAuth2 authority Uri
string authorityUri = "https://login.microsoftonline.com/common/";
// AcquireToken will acquire an Azure access token
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
var token = authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri)).Result.AccessToken;
Console.WriteLine(token);
For more details, see here.

Azure AD Graph - AppRole Creation using Application Credential Flow

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.

Azure B2C - Use msal to get authorization to call Graph API

I recently got started working with B2C. I managed to get their sample application/API using MSAL and an API working with my own tenant.
Now I wanted to:
Figure out how I can run this sample without using an API. The sample uses Scopes to get read/write access to the API. If I remove the references to the API from the app, it no longer works. Surely there should be some way to authenticate to B2C without requiring an API? This is not really important to my application but I'm mostly curious if the webservice HAS to be there as part of the auth-process?
Communicate with Graph Api (Windows or Microsoft Graph?). The sample MS provides uses ADAL and some console application. I cannot find a sample that uses MSAL, so I am having trouble incorporating it into my own application. Is it now possible to call Graph API using MSAL? If it is, is there some documentation on how to do this somewhere?
I tried simply following the docs above and registering an app/granting it permissions. Then putting the client ID/key into my own application (the MSAL one from the first sample), but then I just get a message from B2C that looks like:
Correlation ID: 01040e7b-846c-4f81-9a0f-ff515fd00398
Timestamp: 2018-01-30 10:55:37Z
AADB2C90068: The provided application with ID '9cd938c6-d3ed-4146-aee5-a661cd7d984b' is not valid against this service. Please use an application created via the B2C portal and try again.
It's true that it's not registered via the B2C portal, but that is what the instructions say; to register it in the B2C tenant under App Registrations, not the B2c portal.
The Startup class where all the magic happens looks like:
public partial class Startup
{
// App config settings
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static string ServiceUrl = ConfigurationManager.AppSettings["api:TaskServiceUrl"];
public static string ApiIdentifier = ConfigurationManager.AppSettings["api:ApiIdentifier"];
public static string ReadTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:ReadScope"];
public static string WriteTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:WriteScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };
// B2C policy identifiers
public static string SignUpSignInPolicyId = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string EditProfilePolicyId = ConfigurationManager.AppSettings["ida:EditProfilePolicyId"];
public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicyId;
// OWIN auth middleware constants
public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
// Authorities
public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
/*
* On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
* If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
*/
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
return Task.FromResult(0);
}
/*
* Catch any failures received by the authentication middleware and handle appropriately
*/
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
// Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
// because password reset is not supported by a "sign-up or sign-in policy"
if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
notification.Response.Redirect("/Account/ResetPassword");
}
else if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
/*
* Callback function when an authorization code is received
*/
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
}
With regards to #2, you can only use the Azure AD Graph API with an Azure AD B2C directory, as noted in the "Azure AD B2C: Use the Azure AD Graph API" article.
Here is how (which I have copied from a previous answer)...
Azure AD B2C issues tokens using the Azure AD v2.0 endpoint:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
The Azure AD Graph API requires tokens that are issued using the Azure AD v1.0 endpoint:
https://login.microsoftonline.com/{tenant}/oauth2/token
At design-time:
Register the web application using the Azure AD B2C portal.
Register the web application using the Azure AD portal and grant the Read directory data permission.
At runtime:
The web application redirects the end user to the Azure AD B2C v2.0 endpoint for sign-in. Azure AD B2C issues an ID token containing the user identifier.
The web application acquires an access token from the Azure AD v1.0 endpoint using the application credentials that were created at design-time in step 2.
The web application invokes the Azure AD Graph API, passing the user identifier that was received in step 1, with the access token that was issued in step 2, and queries the user object, etc.
This answer is just addressing question #1, I am unsure on #2 other than it is not publicly documented as far as I know.
B2C does not require you to use an API/service when building your app. In the sample you're looking at it's using 2 libraries to do slightly different (but related) things.
First, it's using an OWIN middleware module to help facilitate the authentication piece. This helps your app identify who a user is, but does not authorize your app to do perform actions or access data on their behalf. This library will net you a session with the end user and basic information about them as well as a authorization code you can use later on.
The other library being used is MSAL. MSAL is a client library for token acquisition and management. In this case after the initial authentication takes place using the aforementioned middleware, MSAL will use the authorization code to get access and refresh tokens that your app can use to call APIs. This is able to take place without end user interaction because they would have already consented to the app (and you would've configured the permissions your app needed). MSAL then manages the refresh and caching of these tokens, and makes them accessible via AcquireTokenSilent().
In order to remove the API functionality from the sample app, you'd need to do a bit more than just remove the scope. Specifically, eliminating the code in the TaskController.cs that is trying to call APIs, remove most usage of MSAL, and likely a few more things. This sample implements a Web App only architecture (Warning: it's for Azure AD not Azure AD B2C. The code is very similar, but would require a bit of modification).

Authenticating as a Service with Azure AD B2C

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.

Azure B2C - Failed to acquire token silently

I'm building an application with ASP.NET MVC and WebAPI using this template : Azure AD B2C WebApp / WepAPI. I've configured my Azure B2C AD through the web.config files and when i click "Sign in" i see my identity providers. Login works so far (i see my username on the top right corner) and i'm able to execute the "To-Do List"-Action.
But a soon as i stop the debugger and restart the Application by pressing F5, i get an error when i click on "To-Do List"-Action again.
Failed to acquire token silently. Call method AcquireToken text --> Code
This happens, cause the user is still authenticated, but the NaiveSessionCache is empty after the applications restart. A possible solution would be, to store the token in the OnAuthorizationCodeReceived Handler, but i looks a little bit weird to me
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
string userObjectID = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant, string.Empty, string.Empty);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
string mostRecentPolicy = notification.AuthenticationTicket.Identity.FindFirst(Startup.AcrClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(notification.Code, new Uri(redirectUri), credential, new string[] { clientId }, mostRecentPolicy);
// Store token in ClaimsIdentity
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim("Token", result.Token));
}
How do i correct retrieve the bearer token using AuthenticationContext-class for further use in my Angular-SPA client?
Is it a good idea to store the token as a claim within the OnAuthorizationCodeReceived Handler?
The solution uses the Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory package. Is Microsoft.IdentityModel.Clients.ActiveDirectory still not supporting Azure B2C ?
Your cache is empty because it is not being persisted anywhere. Check out http://www.cloudidentity.com/blog/2014/07/09/the-new-token-cache-in-adal-v2/. Search for EFADALTokenCache and you will find the implementation that will help you persist the cache to some storage.
Azure B2C will only be supported via the new library called MSAL available at https://www.nuget.org/packages/Microsoft.Identity.Client. This library is still under preview.

Resources