Hi, There
What is Azure AD?
Things you should know already?
2. ASP.Net MVC Framework
Things you need:
2. A Microsoft Azure ID
3. Working internet connection
I have divided this tutorial in 11 steps.
Be patient while doing this because this process will take time.
Step 1: Setting Up SSL URL
- Open any desired ASP.Net MVC Web Application Project in Visual Studio.
- Select Project in solution explorer.
- Press F4 this will open basic project properties.
- Set SSL Enabled to True.
- Copy Auto-Generated SSL URL.
- Right-Click on the project folder and select Properties.
- Select Web => Project URL.
- Paste the Copied SSL URL in this field.
Step 2: App Registration
Step 3: Getting Client Secret
- Go to new app Settings => Keys.
- Enter any Name and Select Duration and Do not enter anything inside value.
- Click Save.
- Copy the value appeared under Value at the moment otherwise it will get disappeared. This is called as Client Secret.
Step 4: Install NuGet Packages
Install the following NuGet packages from top to bottom:-
- Microsoft.IdentityModel.Clients.ActiveDirectory (Only This Version: 2.24.xxxxxxxxx)
- Microsoft.Owin.Security
- Microsoft.Owin.Security.Cookies
- Microsoft.Owin.Security.OpenIdConnect
- Kentor.OwinCookieSaver
- EntityFramework
- Microsoft.Azure.ActiveDirectory.GraphClient
- Microsoft.Owin.Host.SystemWeb
Step 5: Setting Up Configurations
- Open web.config of you web app.
- Navigate to appSettings tag.
- Add the following keys and values:-
- <add key="ida:PostLogoutRedirectUri" value="Copied SSL URL"/>
- <add key="ida:ClientId" value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
- <add key="ida:AADInstance" value="https://login.microsoftonline.com/"/>
- <add key="ida:ClientSecret" value="Copied Client Secret from Step 3"/>
- <add key="ida:Domain" value="yourcompany.onmicrosoft.com"/>
- <add key="ida:TenantId" value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"/>
Your updated web.config should have this structure:-
Step 6: Setting Up Database for Storing Authorization Tokens
- Create a database for storing authorization tokens and cookies.
- Copy the connection string for this database to web.config file.
- Name the connection string as DefaultConnection.
- Don't forget to mention providerName property in connectionstring as System.Data.SqlClient.
Step 7: Creating and Copying Necessary Files
Create the following files and copy the content provided (Note: Don't forget to change the namespaces according to your project and do not change anything instead of namespaces inside the files):
- In application root create file Startup.cs
using Microsoft.Owin;
using Owin;
[assembly: OwinStartupAttribute(typeof(yournamespace.Startup))]
namespace yournamespace
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
- In application root/App_Start folder create file Startup.Auth.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IdentityModel.Claims;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Owin;
using yournamespace.Models;
namespace yournamespace
{
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
public static readonly string Authority = aadInstance + tenantId;
// This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API.
string graphResourceId = "https://graph.windows.net";
public void ConfigureAuth(IAppBuilder app)
{
ApplicationDbContext db = new ApplicationDbContext();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
}
}
});
}
}
}
- In application root/Models folder create file AdalTokenCache.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Security;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace yournamespace.Models
{
public class ADALTokenCache : TokenCache
{
private ApplicationDbContext db = new ApplicationDbContext();
private string userId;
private UserTokenCache Cache;
public ADALTokenCache(string signedInUserId)
{
// associate the cache to the current user of the web app
userId = signedInUserId;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
this.BeforeWrite = BeforeWriteNotification;
// look up the entry in the database
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
// place the entry in memory
this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
}
// clean up the database
public override void Clear()
{
base.Clear();
var cacheEntry = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
db.UserTokenCacheList.Remove(cacheEntry);
db.SaveChanges();
}
// Notification raised before ADAL accesses the cache.
// This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
if (Cache == null)
{
// first time access
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
}
else
{
// retrieve last write from the DB
var status = from e in db.UserTokenCacheList
where (e.webUserUniqueId == userId)
select new
{
LastWrite = e.LastWrite
};
// if the in-memory copy is older than the persistent copy
if (status.First().LastWrite > Cache.LastWrite)
{
// read from from storage, update in-memory copy
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
}
}
this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
}
// Notification raised after ADAL accessed the cache.
// If the HasStateChanged flag is set, ADAL changed the content of the cache
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if state changed
if (this.HasStateChanged)
{
Cache = new UserTokenCache
{
webUserUniqueId = userId,
cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache"),
LastWrite = DateTime.Now
};
// update the DB and the lastwrite
db.Entry(Cache).State = Cache.UserTokenCacheId == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
this.HasStateChanged = false;
}
}
void BeforeWriteNotification(TokenCacheNotificationArgs args)
{
// if you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
}
public override void DeleteItem(TokenCacheItem item)
{
base.DeleteItem(item);
}
}
}
- In application root/Models folder create file ApplicationDbContext.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace yournamespace.Models
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public DbSet<UserTokenCache> UserTokenCacheList { get; set; }
}
public class UserTokenCache
{
[Key]
public int UserTokenCacheId { get; set; }
public string webUserUniqueId { get; set; }
public byte[] cacheBits { get; set; }
public DateTime LastWrite { get; set; }
}
}
Step 8:Authorization and Getting Credentials
- Any method, action or class on which you want authorization decorate it with the [Authorize] attribute.
- For getting credentials you need to again copy some code:-
[Authorize]
public class HomeController : Controller
{
int i = 0;
private ApplicationDbContext db = new ApplicationDbContext();
private string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
private string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private string graphResourceID = "https://graph.windows.net";
public async Task<ActionResult> Index()
{
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
Uri servicePointUri = new Uri(graphResourceID);
Uri serviceRoot = new Uri(servicePointUri, tenantID);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
async () => await GetTokenForApplication());
// use the token for querying the graph to get the user details
var result = await activeDirectoryClient.Users
.Where(u => u.ObjectId.Equals(userObjectID))
.ExecuteAsync(); i++;
IUser user = result.CurrentPage.ToList().First();
- The IUser interface contains all the data that you need like User name of the current user, Name of the logged in user and other data. Remember it doesn't contain password.
Step 9: Logging Out
[Authorize]
public class LogoutController : Controller
{
// GET: Logout
public void Index()
{
Session.RemoveAll();
//return RedirectToAction("Index", "Home");
string callbackUrl = Url.Action("SignOutCallback", "Logout", routeValues: null, protocol: Request.Url.Scheme);
HttpContext.GetOwinContext().Authentication.SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public ActionResult SignOutCallback()
{
if (Request.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToAction("Index", "Home");
}
return RedirectToAction("Index", "Account");
}
}
Step 10: Uploading App to Azure Portal or any other Cloud: Required Changes: 3
- Open web.config => Replace the ida:PostLogoutRedirectUri value with your azurewebsites or cloud URL (Keep it as Https).
- Open Azure Portal => Azure Active Directory => App Registrations => Your App => Manifest => Edit
- Change homepage url with your azurewebsites URL.
- Change replyUrls with your azurewebsites URL.
Step 11: Removing Known Errors
- Problem: Infinite Redirects
Cause :Cookie expiration after logging in into microsoft account
Redirection to http instead of https
Remedy: add app.UseKentorOwinCookieSaver();
Inside root/App_Start/Startup.Auth as shown in screenshot
=> In web.config ==> System.webServer Tag copy
<rewrite>
<rules>
<clear />
<rule name="Redirect to https" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" appendQueryString="false" />
</rule>
</rules>
</rewrite>
- Don't forget to change the database connectionstring from local to global database otherwise it will produce an SQL Server instance not found error.
- If you are getting RequiredNonce error the Clear UserTokenCaches table data created in your database .
You can also watch my video tutorial on YouTube here
I think I have covered everything that can be used while Setting up Azure AD.
If you have any doubts feel free to post in comment.
I will be more than happy to reply.
Thanks for reading the article.
If you like the article please share.




















