Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure Active Directory for authentication and ASP.NET Core Identity for authorization

I would like to create an ASP.NET Core 2.0 application that uses Azure Active Directory as the identity provider (for authentication) but ASP.NET Core Identity for authorization (e.g. using controller attributes like '[Authorize(Roles = "Admin")]'). In the solution, I expect the local Identity database table AspNetUserLogins to hold references to the Azure Active Directory identities

I think the solution would involve claim transformation to decorate the authenticated user with roles fetched from ASP.NET Core Identity.

My problems:

  1. I can get Azure Active Directory authentication working from the Visual Studio Solution template, however I can't figure out how to then add and configure ASP.NET Core Identity (e.g. services.AddIdentity() etc. somewhere in Startup.ConfigureServices())
  2. I'd like to know where the correct hook is to do the claim transformation. (e.g. OpenIdConnectEvents.OnTokenValidated or maybe an AccountController method)

Steps to reproduce my baseline...

  1. In portal.azure.com...

    • Azure Active Directory > App Registrations > New Application Registration (you can delete this later)
    • Give the application a name and set it to 'Wep app / API'
    • Set 'Sign-on URL' to something arbitrary like 'https://blabla'
    • Go to the newly created app registration and copy the 'Application ID'
  2. Create ASP.NET Core 2.0 project using Visual Studio 2017 15.4.2...

    • ASP.NET Core Web Application
    • .NET Framework, ASP.NET Core 2.0, 'Web Application'
    • Change Authentication > 'work or school accounts'
    • select 'cloud - single organization'
    • enter your domain 'something.onmicrosoft.com' (I guess)
    • click through the wizard to create the project
    • edit appsettings.json and change the 'ClientId' to the 'Application ID' (copied from portal.azure.com)
    • copy the value set for 'CallbackPath' (e.g. '/signin-oidc')
    • go to project properties > Debug > and copy the IIS Express https url (e.g. 'https://localhost:44366/')
    • switch back to the new App Registration in portal.azure.com
    • 'Reply URLs' > Add a new reply URL from the concatenation of the two pieces of information above (e.g. 'https://localhost:44366/signin-oidc')
    • click 'Save'
    • Run the project in Visual Studio
    • Login using your Azure Active Directory account (you will be asked to consent to the permissions required by the app)
    • You should then end up at the ASP.NET Core demo page

I'm less certain from here...

(I borrowed code from the template-generated solution with 'authentication options' set to 'Individual User Accounts' > 'Store user accounts in-app'.)

  • Add nuget package Microsoft.AspNetCore.Identity.EntityFrameworkCore (I had to upgrade Microsoft.AspNetCore.Authentication.Cookies from 2.0.0 to 2.0.1 first though for it to install)
  • Add nuget package Microsoft.EntityFrameworkCore.SqlServer
  • Add the following classes

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext(DbContextOptions<AspNetCoreIdentity.Data.ApplicationDbContext> options) : base(options)
        {
        }
    
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }
    
    public class ApplicationUser : IdentityUser
    {
    }
    
  • Add the following at the beginning of Startup.ConfigureServices()

    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();
    
  • Add connection string to appsettings.json (assumes default SQL Server instance on localhost and identity database named 'AspNetCoreIdentity')

    "ConnectionStrings": {
      "DefaultConnection": "Data Source=.\\;Initial Catalog=AspNetCoreIdentity;Integrated Security=True;MultipleActiveResultSets=True"
    }
    

Now when I run the app again, I end up in a redirect loop which I think runs between my app and Azure Active Directory sign on. Tracing shows...

Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed for user: (null). Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker: Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.

I then tried adding methods to AccountController (Login, ExternalLogin) in the hope that I could hit a breakpoint, but now I'm really stuck.

other references...

  • http://www.blinkingcaret.com/2016/11/30/asp-net-identity-core-from-scratch
  • http://www.blinkingcaret.com/2017/05/03/external-login-providers-in-asp-net-core
  • https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity
  • asp.net core identity extract and save external login tokens and add claims to local identity
  • ASP.NET Identity: Update external claims after authorization
  • SignInManager,what it is and how,when to use?
like image 452
jimalad Avatar asked Dec 04 '17 22:12

jimalad


People also ask

How do you implement Azure AD authentication in .NET Core?

Select ASP.NET Core Web Application>Choose Web Application (Model-View-Controller) template> Click on the "Change Authentication" button>Select "Work or School Accounts". Choose Cloud - Single Organization. Fill up the field of Domain which is the Azure Active Directory tenant name (say, softdreams.onmicrosoft.com).

How would you implement Azure AD authentication in ASP NET application?

In the New ASP.NET Project dialog, select MVC, and then click Change Authentication. On the Change Authentication dialog, select Organizational Accounts. These options can be used to automatically register your application with Azure AD as well as automatically configure your application to integrate with Azure AD.

Does Active Directory do authentication and authorization?

What is Active Directory Authentication and Authorization? Active Directory is a directory service implemented by Microsoft for Windows domain networks. An Active Directory domain controller authenticates and authorizes users in a Windows-domain network by enforcing security policies for all computers.


1 Answers

I think I have this working, but I'm pretty new to this framework, so critiques to this method are welcome.

In startup, I had to add two things to the example from Microsoft.

  1. Set the DefaultSignInScheme value to AuthenticationProperties to prevent a Stackoverflow exception when authorization fails (see here).
  2. Add the access denied path to the application cookie

Startup.cs:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        })
        .AddAzureAd(options => Configuration.Bind("AzureAd", options))
        .AddCookie(options =>
        {
            options.AccessDeniedPath = "/AccessDenied";
        });
   // Remaining code removed

I then extended the AzureAdAuthenticationBuilderExtensions > ConfigureAzureOptions class and am doing some additional work (i.e. loading the user roles from whatever the role store is) when the token validation event occurs

AzureAdAuthenticationBuilderExtensions.cs

public void Configure(string name, OpenIdConnectOptions options)
        {
            options.ClientId = _azureOptions.ClientId;
            options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
            options.UseTokenLifetime = true;
            options.CallbackPath = _azureOptions.CallbackPath;
            options.RequireHttpsMetadata = false;

            options.Events = new OpenIdConnectEvents
            {                    
                OnTokenValidated = (context) =>
                {                           
                    // Load roles from role store here
                    var roles = new List<string>() { "Admin" };
                    var claims = new List<Claim>();
                    foreach (var role in roles) claims.Add(new Claim(ClaimTypes.Role, role));

                    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                    context.Principal.AddIdentity(claimsIdentity);

                    return Task.CompletedTask;       
                }                    
            };
        }
like image 123
gwstroup Avatar answered Oct 08 '22 02:10

gwstroup