Asp.net mvc azure ad

De Banane Atomic
Aller à la navigationAller à la recherche

Description

Une application web ASP.NET MVC avec une authentification Azure AD App.

Visual Studio

  • New Project → Web → ASP.NET Web Application (.NET Framework)
  • Template: MVC (+ Web API)
  • Authentification: No Authentification (Work or School Accounts)

Configuration projet ASP.NET

Startup.cs
public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureAuth(app);
    }
}
App_Start\Startup.Auth.cs
public partial class Startup
{
    private static string _clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string _clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];  // password
    private static string _tenant = ConfigurationManager.AppSettings["ida:Tenant"];
    private static string _authority = "https://login.microsoftonline.com/" + _tenant;
    private static string _postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];

    // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = _clientId,
            //ClientSecret = _clientSecret,
            Authority = _authority,
            //RedirectUri = _postLogoutRedirectUri,
            PostLogoutRedirectUri = _postLogoutRedirectUri,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = context =>
                {
                    context.HandleResponse();
                    context.Response.Redirect("/Error/message=" + context.Exception.Message);
                    return Task.FromResult(0);
                },
                AuthorizationCodeReceived = context =>
                {
                    var code = context.Code;
                    var credential = new ClientCredential(_clientId, _clientSecret);
                    string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                    var authContext = new AuthenticationContext(_authority);
                    AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                        code, 
                        new Uri(_postLogoutRedirectUri),
                        // new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path))
                        credential, 
                        "https://graph.microsoft.com");  // Graph Resource Url
                    var graphToken = result.AccessToken;
                }
            }
        });
    }
}
Web.config
<configuration>
  <appSettings>
    <add key="ida:Tenant" value="xxx.onmicrosoft.com" />
    <add key="ida:Audience" value="https://xxx.onmicrosoft.com/1234abcd-1234-abcd-1234-123456abcdef" />
    <add key="ida:ClientID" value="5678abcd-5678-abcd-5678-567890abcdef" />
    <add key="ida:PostLogoutRedirectUri" value="https://yyy.azurewebsites.net/" />

Packages Nuget

Microsoft.Owin.Security.OpenIdConnect
Microsoft.Owin.Security.Cookies
Microsoft.Owin.Security
Owin IAppBuilder
Microsoft.IdentityModel.Clients.ActiveDirectory ClientCredential
Microsoft.Owin.Host.SystemWeb exécution de Startup.cs au démarrage

Configuration Azure AD

Azure → Azure Active Directory → App Registrations

Bouton SignIn

Views/Shared/_LoginPartial.cshtml
@if (Request.IsAuthenticated)
{
    <ul class="nav navbar-nav navbar-right">
        <li class="navbar-text">
            Hello @User.Identity.Name !
        </li>
        <li>
            @Html.ActionLink("Sign out", "SignOut", "Account")
        </li>
    </ul>
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues:null, htmlAttributes: new { id = "loginLink" })</li>
    </ul> 
}
Views/Shared/_Layout.cshtml
@Html.Partial("_LoginPartial")
Controllers/AccountController.cs
public class AccountController : Controller
{
    public void SignIn()
    {
        if (!Request.IsAuthenticated)
        {
            HttpContext.GetOwinContext().Authentication.Challenge(
                new AuthenticationProperties { RedirectUri = "/" },
                OpenIdConnectAuthenticationDefaults.AuthenticationType);
        }
    }

    public void SignOut()
    {
        HttpContext.GetOwinContext().Authentication.SignOut(
            OpenIdConnectAuthenticationDefaults.AuthenticationType,
            CookieAuthenticationDefaults.AuthenticationType);
    }

Token Cache

App_Start\Startup.Auth.cs
AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
ADALTokenCache.cs
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)
        {
            if (Cache == null)
            {
                Cache = new UserTokenCache
                {
                    webUserUniqueId = userId
                };
            }

            Cache.cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache");
            Cache.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);
    }
}

OLD

Azure AD - Browser to Web App.svg

  1. New Project → Web → ASP.NET Web Application → MVC, No Authentication
  2. Sélectionner le projet → onglet Properties → SSL enabled → True
    1. Copier la SSL URL
  3. Clique-droit sur le projet → Properties → Web → Servers → Project Url → https://<SSL URL>
  4. Installer les paquets Nuget:
    • Microsoft.IdentityModel.Protocol.Extensions
      • System.IdentityModel.Tokens.Jwt
    • Microsoft.Owin.Security.Cookies
    • Microsoft.Owin.Host.SystemWeb
    • Microsoft.Owin.Security.OpenIdConnect
Web.config
<configuration>
  <appSettings>
    <add key="ida:ClientId" value="" />
    <add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
    <!-- Domaine du compte Azure -->
    <add key="ida:Tenant" value="xxx.onmicrosoft.com" />
    <!-- Project URL -->
    <add key="ida:PostLogoutRedirectUri" value="https://localhost:44350/" />
App_Start/Startup.cs
using Microsoft.Owin.Security;

// à déclarer en dehors du namespace
// exécute la méthode Configuration du type passé en paramètre
[assembly: OwinStartup(typeof(Namespace.App_Start.Startup))]
// assembly Microsoft.Owin

public class Startup
{
    private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
    private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
    private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];

    // https://login.microsoftonline.com/xxx.onmicrosoft.com
    string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

    public void Configuration(IAppBuilder app)
    {
        // assembly Microsoft.Owin.Security
        // assembly Microsoft.Owin.Security.Cookies
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        // assembly Microsoft.Owin.Security.OpenIdConnect
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            Authority = authority,
            PostLogoutRedirectUri = postLogoutRedirectUri,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthenticationFailed = context =>
                {
                    context.HandleResponse();
                    context.Response.Redirect("/Error/message=" + context.Exception.Message);
                    return Task.FromResult(0);
                }
            }
        });
Views/Shared/_LoginPartial.cshtml
@if (Request.IsAuthenticated)
{
    <ul class="nav navbar-nav navbar-right">
        <li class="navbar-text">
            Hello @User.Identity.Name !
        </li>
        <li>
            @Html.ActionLink("Sign out", "SignOut", "Account")
        </li>
    </ul>
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues:null, htmlAttributes: new { id = "loginLink" })</li>
    </ul> 
}
Views/Shared/_Layout.cshtml
@Html.Partial("_LoginPartial");
Controllers/AccountController.cs
public class AccountController : Controller
{
    public void SignIn()
    {
        if (!Request.IsAuthenticated)
        {
            HttpContext.GetOwinContext().Authentication.Challenge(
                new AuthenticationProperties { RedirectUri = "/" },
                OpenIdConnectAuthenticationDefaults.AuthenticationType);
        }
    }

    public void SignOut()
    {
        HttpContext.GetOwinContext().Authentication.SignOut(
                OpenIdConnectAuthenticationDefaults.AuthenticationType,
                CookieAuthenticationDefaults.AuthenticationType);
    }

Créer une application dans Azure Active Directory

Azure → Azure Active Directory → App registration → New application registration

  • Sign-on URL: https://<SSL URL>

Récupérer l'Application ID (= Client ID) et la reporter dans Web.config

  • Azure → Azure Active Directory → App registration → My App → Application ID