Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use both internal Forms authenticationas well as Azure AD authentication

In my web application I have forms authentication implemented by default. But for internal users I use AD for authentication. I had an on-premise AD exposed over LDAP which I used just to authenticate internal users.

Now we are planning to move to azure. So we have migrated to azure AD.

The problem with Azure AD is I can't no longer use the form inside my page to authenticate users. I have to redirect users to azure's authentication page (OWIN OpenIDConnect). Is there a way I can have the username/password in my local application and then send them to any azure api/service to authenticate it?

The other problem is - Now I cannot use forms authentication as default in my web.config or it never redirects to the azure's authentication page.

Can anyone please help?

Note: It is a single tenant WEB Application with multiple users

like image 838
Jatin Nath Prusty Avatar asked Jul 22 '16 03:07

Jatin Nath Prusty


2 Answers

The problem can be solved using OWIN CookieAuthentication and ASP.NET identity to handle the local authentication with OpenIdConnect for the AzureAD authentication.

"The other problem is - Now I cannot use forms authentication as default in my web.config or it never redirects to the azure's authentication page."

If you do not set <authentication mode="none" /> in the web.config OWIN cannot take over the Authentication/Authorization process for your application from IIS.

In your Startup class you will need to setup both your Identity and OpenIdConnect pipelines.

Identity

If you generate a new MVC project using Individual accounts for authentication you can see the auto generated code for the Startup class (example below) in addition to the Account controller which has Login Actions for both Direct Authentication and ExternalLogin (3rd party). You can also see how you can map an external login provider (AzureAD in your case) to a local user account i.e. one in your applications local identity store by looking at the ExternalLogin and ExternalLoginCallback actions in the Account controller.

Tutorial on setting up minimum required for local identity authentication.
Adding-minimal-OWIN-Identity-Authentication-to-an-Existing-ASPNET-MVC-Application

OpenIdConnect

When setting up the OpenIdConnect if you set the Notification RedirectToIdentityProvider (as shown below) with the domain_hint parameter set to a verified domain under the domains section which has single sign-on in your Azure Active Directory (Azure Management Portal) a user should skip the prompt page if already authenticated on a domain joined machine.

domain_hint - This parameter is not part of the OpenID Connect standard. Azure AD introduced it to allow you to specify which IdP you want users to authenticate with. Say that your app trusts a federated Azure AD tenant. With the default request, users would first see the Azure AD authentication pages and be redirected to the federated ADFS only after they type their username in the text box. If you send the domain_hint parameter set to the federated domain, the Azure AD page is skipped, and the request goes straight to the ADFS associated with the tenant. If the user is accessing your app from an Intranet, and is thus already authenticated with ADFS, this can actually enable a seamless single sign-on experience.

Modern Authentication with Azure Active Directory for Web Applications p.118


Domain tab located under your tenant
Domain tab located under your tenant


There are also other parameters such as prompt which can be useful for controlling authentication flow.

http://openid.net/specs/openid-connect-core-1_0.html

Startup.Auth

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {

        // Configure the db context, user manager and signin manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
        
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<SystemUserManager, SystemUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });
        
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {

                ClientId = clientId,
                Authority = Authority,
                PostLogoutRedirectUri = postLogoutRedirectUri,

                // Used to skip login prompt for trusted tenant
                
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    RedirectToIdentityProvider = async (context) =>
                    {
                        context.ProtocolMessage.DomainHint = "someDomainName";
                    },
                }
            });
        }
    }

When an unauthenticated user tries to access protected code they will be directed to the Azure login page. One way you can deal with this is by creating a custom attribute and that directs them to to the desired login page.

public class ExampleAuthorize : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        return httpContext.User.IsInRole(this.Roles);
    }


    protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
        {
            {"action", "Login"},{"controller", "Account"}
        });
    }
}

**Update to Comment**

The Resource Owner Password Credentials Grant (i.e., username and password) flow can be used directly as an authorization grant however it should only be used where entirely necessary (see accepted answer) How to accept user credentials programmatically. Example project Authenticating to Azure AD non-interactively using a username & password

If it is just a customised AzureAD Login page to achieve a consistent look and feel then adding AzureAD custom branding is an option.

Untested -

You could try setting the username and password parameters on the RedirectToIdentityProvider request. According to the OpenIDConnect Spec Setting prompt="none" should tell the server to "NOT display any authentication or consent user interface pages" (see code and comments).

middleware setup -

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {

        ClientId = clientId,
        Authority = "https://login.windows.net/yourcompany.onmicrosoft.com",
        PostLogoutRedirectUri = postLogoutRedirectUri,                   
        Notifications = new OpenIdConnectAuthenticationNotifications()
        {                        
            RedirectToIdentityProvider = async (context) =>
            {
                context.ProtocolMessage.Username = context.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary["username"];
                context.ProtocolMessage.Password = context.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary["password"];
                
                // Its my understanding that setting prompt="none" should  tell the server not to display any authentication or consent user interface pages however this has not worked for me                 
                // context.ProtocolMessage.Prompt = "none";
            },
        }
    });
    

In your Login action -

public void Login(string username, string password, string redirectUri, string loginProvider)
{
    AuthenticationProperties properties = new AuthenticationProperties();       
    properties.RedirectUri = RedirectUri;
    properties.Dictionary.Add("username", username);
    properties.Dictionary.Add("password", password);

    context.HttpContext.GetOwinContext().Authentication.Challenge(properties, loginProvider);
}
like image 89
clD Avatar answered Sep 27 '22 01:09

clD


After lot of research on this I conclude that:

1) In web application it is not possible to pass the username/password directly to azure AD for authentication just the way it was done using on-premise AD (LDAP). This is not a limitation but could be thought of as a security implementation.

2) At most you can skip the azure portal page by using RedirectToIdentityProvider and domain_hint.

3) ADAL where it is possible to pass user credential to azure AD for authentication is only applicable for native clients where the native client wants to allow the user access to another resource (web api, service etc.)

4) To use azure AD on top of forms based authentication you have to set the authentication mode to none in web.config and create a new authorization filter to redirect unauthorized users to the forms based login page. Or you can use the answer in the following URL: Redirect user to custom login page when using Azure AD

like image 44
Jatin Nath Prusty Avatar answered Sep 26 '22 01:09

Jatin Nath Prusty