How to authorize in Azure Active Directory without using dialog? - asp.net-mvc

My application shows dashboard of my power bi account for all users, I am authorizing the Azure Active Directory through a dialog to get an access token. Can I hard code my credentials and get access token without using the authorization dialog.
Code. It works but it is using the authorization dialog.
var #params = new NameValueCollection
{
{"response_type", "code"},
{"client_id", Properties.Settings.Default.ClientID},
{"resource", "https://analysis.windows.net/powerbi/api"},
{"redirect_uri", "http://localhost:13526/Redirect"}
};
var queryString = HttpUtility.ParseQueryString(string.Empty);
queryString.Add(#params);
string authorityUri = "https://login.windows.net/common/oauth2/authorize/";
var authUri = String.Format("{0}?{1}", authorityUri, queryString);
Response.Redirect(authUri);
Redirect.aspx
string redirectUri = "http://localhost:13526/Redirect";
string authorityUri = "https://login.windows.net/common/oauth2/authorize/";
string code = Request.Params.GetValues(0)[0];
TokenCache TC = new TokenCache();
AuthenticationContext AC = new AuthenticationContext(authorityUri, TC);
ClientCredential cc = new ClientCredential
(Properties.Settings.Default.ClientID,
Properties.Settings.Default.ClientSecret);
AuthenticationResult AR = AC.AcquireTokenByAuthorizationCode(code, new Uri(redirectUri), cc);
Session[_Default.authResultString] = AR;
Response.Redirect("/Default.aspx");
Default.aspx
string responseContent = string.Empty;
System.Net.WebRequest request = System.Net.WebRequest.Create(String.Format("{0}dashboards", baseUri)) as System.Net.HttpWebRequest;
request.Method = "GET";
request.ContentLength = 0;
request.Headers.Add("Authorization", String.Format("Bearer {0}", authResult.AccessToken));
using (var response = request.GetResponse() as System.Net.HttpWebResponse)
{
using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
{
responseContent = reader.ReadToEnd();
PBIDashboards PBIDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseContent);
}
}

I did this once without using ADAL. For Power BI as well, since they don't offer application permissions, only delegated.
Note: This method won't work if the user has MFA enabled, their password has expired etc.
In general you'll want to use interactive flows.
You can even have a bootstrapping process where the user logs in interactively and you store the refresh token received.
That refresh token can then be used in the background as long as it works.
What you need to is call the AAD token endpoint with grant_type=password. You will specify the username and password, as well as the client id, client secret and resource URI in form parameters.
Here is the function I wrote:
private async Task<string> GetAccessToken()
{
string tokenEndpointUri = Authority + "oauth2/token";
var content = new FormUrlEncodedContent(new []
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", Username),
new KeyValuePair<string, string>("password", Password),
new KeyValuePair<string, string>("client_id", ClientId),
new KeyValuePair<string, string>("client_secret", ClientSecret),
new KeyValuePair<string, string>("resource", PowerBiResourceUri)
}
);
using (var client = new HttpClient())
{
HttpResponseMessage res = await client.PostAsync(tokenEndpointUri, content);
string json = await res.Content.ReadAsStringAsync();
AzureAdTokenResponse tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponse>(json);
return tokenRes.AccessToken;
}
}
Authority here is https://login.microsoftonline.com/tenant-id/. Here is the response class I'm using:
class AzureAdTokenResponse
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
}

I hope using UserCreadential you have give username and password of azure subscription and you can get AccessToken and call your api. i hope it should helps you.
string ResourceUrl="https://analysis.windows.net/powerbi/api";
string ClientId=Properties.Settings.Default.ClientID;//as per your code
AuthenticationContext authenticationContext = new AuthenticationContext(Constants.AuthString, false);
UserCredential csr = new UserCredential("your-username", "password");
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(ResourceUrl,ClientId, usr);
string token = authenticationResult.AccessToken;

Related

How to add JwT token in request header ASP.Net MVC Core 6

I have just started to use Asp.Net Core and I managed to create a mvc project. In This project I have created an API and it is secured with token based authorization.I have also used identity framework for user auhentication. Now I want to consume this API to perform CRUD operations with passing token but have no clear idea how to do that. After searching similar questions what I have tried is generate the token using user credentials (username, password) when user successfully logged in or registered and attach the generated token to header and as far as I know it will be passed through each subsequent request.
First I tried creating a method to call to generate the token after success login or registration. This includes in same controller which used for login and registration.
Token generate method
public string GenerateAuthToken(ApplicationUser applicationUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub, applicationUser.Id),
new Claim(type:JwtRegisteredClaimNames.Email, applicationUser.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
I call this after success user login and register,
public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = registerViewModel.Username,
Email = registerViewModel.Email};
var result = await _userManager.CreateAsync(user, registerViewModel.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
var token = GenerateAuthToken(user);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("bearer", token);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "User Registration Failed");
}
return View(registerViewModel);
}
When this executed, the token is successfully generated but does not attach the token. I do not know if I am doing any wrong here. But I found someone facing the same issue but has tried different way to achieve this. I think it is the correct way but not sure. Instead of generate the token on success login, have to generate it each api call. According to this solution I created another controller and action to generate the token.
public async Task<IActionResult> GetToken([FromBody] AuthViewModel authViewModel)
{
var user = _context.Users.FirstOrDefault(u => u.Email == authViewModel.Email);
if (user != null)
{
var signInResult = await _signInManager.CheckPasswordSignInAsync(user,
authViewModel.Password, false);
if (signInResult.Succeeded)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")
["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub,authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Email,
authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new
SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return Ok(new { Token = stringToken });
}
return BadRequest("Invalid User");
}}
AuthViewModel
public class AuthViewModel
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
I added authViewModel to accept logged user credentials since I don't want add them manually, Then I have created another controller to perform the CRUD same as the above mentioned link Please note that I followed the solution mentioned below that page.
private async Task<string> CreateToken()
{
var user = await _userManager.GetUserAsync(User);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7015/Auth");
request.Content = JsonContent.Create(new AuthViewModel{
Email = user.Email, Password = user.PasswordHash
});
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request);
var token = await response.Content.ReadAsStringAsync();
HttpContext.Session.SetString("JwToken", token);
return token;
}
request.Content I added to match my solution since token should be generated using user credentials. But I have no idea how to pass the logged in user's credentials with the request. This does not work. It is not possible to access the user password.
This is how I called the token generate action to perform CRUD. And I use JQuery Ajax to call the GetAllSales endpoint.
public async Task<IActionResult> GetAllSales()
{
string token = null;
var strToken = HttpContext.Session.GetString("JwToken");
if (string.IsNullOrWhiteSpace(strToken))
{
token = await CreateToken();
}
else
{
token = strToken;
}
List<Sale> sales = new List<Sale>();
var client = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get,
"http://localhost:7015/api/Sales");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiString = await response.Content.ReadAsStringAsync();
sales = JsonConvert.DeserializeObject<List<Sale>>(apiString);
}
Ok(sales);
}
This does not work. An exception throws
'System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)'
Please suggest me and show me how to achieve this correctly. I am expecting all of your help. Thank you.
System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'
This issue means you inject IHttpClientFactory in AccessApiController without registering the service in Program.cs.
Register IHttpClientFactory by calling AddHttpClient in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();

Calling Graph API from Console Application using a User Credentials

I'm using following code to call Microsoft Graph API:
private static void GraphAPICallTest()
{
try
{
string authority = "https://login.microsoftonline.com/common/oauth2/token";
string resrouce = "https://graph.microsoft.com";
string clientId = "{clientid}";
string userName = "userid#test.com";
string password = "{userpassword}";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resrouce, clientId, userPasswordCredential).Result;
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
var access_token = authContext.AcquireTokenSilentAsync(resrouce, clientId).Result.AccessToken;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
return Task.FromResult(0);
}));
var a = graphserviceClient.Me.Request().GetAsync().Result;
Console.WriteLine("Success");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
}
Console.ReadLine();
}
When I use any user from Azure AD this code works just fine. but if i use personnel account it errors out with following error.
ADSTS65001: The user or administrator has not consented to use the application with ID 'c15fd791-7a49-424b-a938-2a9464476277' named 'OneDriveWebAppTest'. Send an interactive authorization request for this user and resource.
Trace ID: 5f31be27-dfdd-410b-af41-e769b2573d00
Correlation ID: 9c8c9f60-c56d-4845-a223-37f5232c6210
Timestamp: 2017-12-11 13:58:39Z
Yes, error is asking about consenting with that personnel user. But even after doing the consent with that user, the error still persist.
Not sure if i'm missing anything.

Using the client_credentials flow with ADFS 4.0 returns 401

I have the "Server application accessing a web API" scenario.
The web site uses OIDC and authenticates no problem.
However, I have a use case for accessing some of the web API without a user context and for that, I use client_credentials.
The server app has a client ID and the secret key.
So assume the web API URL is:
https://my-pc/WebService/api/my-api/
The web API has the RP identifier:
https://my-pc/WebService/api/my-api/
Access control policy is:
Permit everyone
I have one claim rule:
c:[] => issue(claim = c);
Client permissions is set to:
"All clients" with scope of openid and user_impersonation.
The code is:
AuthenticationContext ac = new AuthenticationContext("https://my-adfs/adfs/", false);
// ClientCredential contains client_id and secret key
AuthenticationResult result = await ac.AcquireTokenAsync("https://my-pc/WebService/api/my-api/", clientCredential);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://my-pc/WebService/api/my-api/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("foo", "blah"), new KeyValuePair<string, string>("foo1", "blah1") });
request.Content = content;
HttpResponseMessage response = await client.SendAsync(request);
ADFS returns the access token no problem but when I call the web api, I keep getting a 401 - Unauthenticated.
Any ideas?
Finally figured this out and wrote it up.
The two pieces of code:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
ClientCredential clientCredential = new ClientCredential(clientId, secretKey);
AuthenticationContext ac = new AuthenticationContext("https://my-adfs/adfs/", false);
AuthenticationResult result = await ac.AcquireTokenAsync("https://my-pc/WebService",
clientCredential);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
"https://my-pc/WebService/api/my-api/");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string,
string>("foo", "blah"), new KeyValuePair<string, string>("foo1", "blah1") });
request.Content = content;
HttpResponseMessage response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
// etc
And in Startup.Auth.cs
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters()
{
SaveSigninToken = true,
ValidAudience = "https://my-pc/WebService"
},
MetadataEndpoint = "https://my-adfs/FederationMetadata/2007-06/FederationMetadata.xml"
});
Took me a while!
Since you don't get the user principal in the access token, you have no user session attached to a principal. When the Web API looks for the principal in the cookie(or Authorization header if you're sending access token) it doesn't find the principal and you will still be an anonymous user.

InvalidAuthenticationToken when trying to invoke Microsoft Graph client

I'm following a sample provided at the Microsoft Graph repository on github. In it, it describes using the DelegateAuthenticationProvider which needs an access token.
Marc LaFleur's primer on auth flow specified a way to get the access token which I combined with guidance on making service-service authentication work.
Even though I receive what appears to be a valid access token, when I issue the command via the graph client, I get a ServiceException from Microsoft Graph stating,
"{
Code: InvalidAuthenticationToken
Message: Access token validation failure.
Inner error
}"
I haven't seen any other inner error.
What am I doing incorrectly?
This is the code I've come up:
var clientId = "[app-guid]";
var clientSecret = "[secret-from-app-dashboard]";
var resource = "[app-guid]";
var baseUrl = "https://login.microsoftonline.com";
var loginUrl = "/[tenant guid]/oauth2/token";
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(baseUrl);
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("resource", resource)
});
var result = client.PostAsync(loginUrl, content).Result;
string resultContent = result.Content.ReadAsStringAsync().Result;
var json = JObject.Parse(resultContent);
accessToken = (string)json["access_token"];
}
var header = new AuthenticationHeaderValue("Bearer", accessToken);
var graphserviceClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = header;
return Task.FromResult(0);
}));
try
{
var users = await graphserviceClient.Users.Request().GetAsync();
var user = users.First();
}
catch (Microsoft.Graph.ServiceException servex)
{
Console.WriteLine(servex);
}
You set your resource to point to your website, but it should point to the external web resource you wish to access. Try this:
var resource = "https://graph.microsoft.com/";

ASP.NET MVC 5 Dynamic Azure AD connectivity

I am currently building a SaaS - application with ASP.NET MVC 5 as the core platform. One of the requirements is to allow tenants to login with their Azure AD Accounts.
Here is the background story:
It's a multi tenant application where each customer/tenant can have multiple users. Tenants share the same instance of the application, so they will ultimately all navigate to the same URL (myapplication.com). We allow users to login with their organizational accounts (Azure AD, Office 365). Since all tenants go to the same URL, we cannot determine upfront which client ID/secret to use as this will only work for just one client. By using subdomains, we can identify the client (e.g. client1.ourapplication.com), so we can retrieve the Azure AD data from the database.The next step would be to use pass this data to the OpenID Authentication Options for each request that comes in, instead of 'hard coding' it in compile time during the startup of the application.
The issue that I'm currently facing is how I can get rid of the hard coded ClientId and AppKeys in the Web.Config (or database, as is in this case) and dynamically change these values according to the user that is logging in. In other words, I need to create the Azure AD login provider on the fly for every login request.
In the full code sample below, I somehow need to be able to set this line app.UseOpenIdConnectAuthentication(...) during runtime and not during the startup of the application with the updated clientID and appKey of the tenant in question.
public void ConfigureAuth(IAppBuilder app)
{
string clientId = string.Empty;
string appKey = string.Empty;
ConfigurationWorker worker = new ConfigurationWorker(DependencyResolver.Current.GetService<IUnitOfWork>());
IEnumerable<Config> azureAdConfiguration = worker.GetConfiguration("AzureId", "AzurePassword").Result;
if (azureAdConfiguration != null && azureAdConfiguration.Count() == 2)
{
clientId = azureAdConfiguration.First(x => x.Key == "AzureId").Value;
appKey = azureAdConfiguration.First(x => x.Key == "AzurePassword").Value;
}
string graphResourceID = "https://graph.windows.net";
string Authority = "https://login.microsoftonline.com/common/";
app.UseOpenIdConnectAuthentication(this.GetAzureADLoginProvider(clientId, appKey, Authority, graphResourceID));
}
private OpenIdConnectAuthenticationOptions GetAzureADLoginProvider(string clientId, string appKey, string Authority, string graphResourceID)
{
return new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
AuthenticationMode = AuthenticationMode.Passive,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.microsoftonline.com/{0}", tenantID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code,
new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)),
credential,
graphResourceID);
return Task.FromResult(0);
},
RedirectToIdentityProvider = (context) =>
{
// This ensures that the address used for sign in and sign out is picked up dynamically from the request
// this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
// Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
return Task.FromResult(0);
},
// we use this notification for injecting our custom logic
SecurityTokenValidated = (context) =>
{
// Once user has logged in, create and replace existing claims identity
ClaimsIdentity newClaimsIdentity = ClaimsHelper.Create(context.AuthenticationTicket.Identity, (x) => Singleton.Instance().Logger.LogInformation(x));
// Ultimately add tenant ID from azure to claims identity
Claim tenantIdClaim = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid");
newClaimsIdentity.AddClaim(tenantIdClaim);
// Set context's user to the new claim identity
context.AuthenticationTicket = new AuthenticationTicket(newClaimsIdentity, context.AuthenticationTicket.Properties);
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
return Task.FromResult(0);
}
}
});
}
This is the AccountController's action that will be executed if the user selects to login with Azure AD:
[AllowAnonymous]
public void SignInAzure()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
this.AuthenticationManager.Challenge(
new AuthenticationProperties { RedirectUri = Url.Action("AzureADCallback", new { returnUrl = "~/" }) },
OpenIdConnectAuthenticationDefaults.AuthenticationType
);
}
}
Can I somehow set the clientID and AppKey in this controller? Because at this point I'm able to determine the tenant (using the subdomain), which allows me to query the correct values (in the database).
Something like this pseudocode would be awesome to have:
this.AuthenticationManager.OpenIdConnectAuthenticationOptions.ClientID= NEWID;
this.AuthenticationManager.OpenIdConnectAuthenticationOptions.AppKey= NEWAPPKEY;
Or equally as well create the OpenIdConnectAuthenticationOptions object on the fly per request.
Is there any possibility to achieve this?
Update:
As a workaround for this issue, I currently use this approach, as suggested by this solution:
[AllowAnonymous]
public async Task<ActionResult> SignInAzure()
{
// Resolve tenant
string currentTenant = this.HttpContext.Request?.RequestContext?.RouteData?.Values["tenant"]?.ToString();
IEnumerable<Config> configKeys = await this.ConfigurationWorker.GetConfiguration(currentTenant, ConfigurationKeys.AzureId, ConfigurationKeys.AzurePassword);
string clientId = string.Empty;
string appKey = string.Empty;
if (configKeys != null && configKeys.Count() == 2)
{
clientId = configKeys.First(x => x.Key == ConfigurationKeys.AzureId).Value;
appKey = configKeys.First(x => x.Key == ConfigurationKeys.AzurePassword).Value;
}
string stateMarker = Guid.NewGuid().ToString();
string returnUrl = this.Request.Url.GetLeftPart(UriPartial.Authority).ToString() + "/Account/AzureADCallback";
string authorizationRequest = String.Format(
"https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id={0}&resource={1}&redirect_uri={2}&state={3}",
Uri.EscapeDataString(clientId),
Uri.EscapeDataString("https://graph.windows.net"),
Uri.EscapeDataString(returnUrl),
Uri.EscapeDataString(stateMarker)
);
authorizationRequest += String.Format("&prompt={0}", Uri.EscapeDataString("admin_consent"));
return new RedirectResult(authorizationRequest);
}
[AllowAnonymous]
public async Task<ActionResult> AzureADCallback(string code, string error, string error_description, string resource, string state)
{
// Resolve tenant
string currentTenant = this.HttpContext.Request?.RequestContext?.RouteData?.Values["tenant"]?.ToString();
IEnumerable<Config> configKeys = await this.ConfigurationWorker.GetConfiguration(ConfigurationKeys.AzureId, ConfigurationKeys.AzurePassword);
string clientId = string.Empty;
string appKey = string.Empty;
if (configKeys != null && configKeys.Count() == 2)
{
clientId = configKeys.First(x => x.Key == ConfigurationKeys.AzureId).Value;
appKey = configKeys.First(x => x.Key == ConfigurationKeys.AzurePassword).Value;
}
ClientCredential credential = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common/");
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(Request.Url.GetLeftPart(UriPartial.Path)), credential);
var user = this.UserManager.FindByEmail(result.UserInfo.DisplayableId);
await this.SignInAsync(user, false);
return RedirectToAction("Index", "Home");
}
In a nutshell, the tenant is resolved from the URL (subdomains) after which the database is called to get the Azure ID and Secret. This is used to generate the URL for the user to login. After the user has logged in to Azure AD, an AuthenticationResult is generated and the user is signed in to the application.

Resources