Search code examples
c#asp.net-mvcazurewebformsasp.net-identity

How to prevent [Authorize (Roles = "xxx")] to verify in openidconnect when using webform authentification


In my login web page, I have a webform (Identity 2.0) you can use to create an account in a database or you can use Azure Active Directory to authenticate with a corporate email. (External auth)

I've put the [Authorize] attribute to decorate the Index() action in UserController. My List() action in the same controller is decorated like this : [Authorize (Roles = "Admin")]

When logged in with my webform login, if I go to /MyController/List/ I'm redirected to the Microsoft account Login page. When going to /MyController/Index I'm not redirected.

What is causing this behavior? I don't want to check in Azure when the user is logged in with webform. How can I prevent this from happening?

Here's my Startup.Auth.cs

    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";


    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {

        // Configure the db context, user manager and role manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
        app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
            }
        });

        // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

        // Enables the application to remember the second login verification factor such as phone or email.
        // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
        // This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

        // Pour Azure
        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            Authority = Authority,
            PostLogoutRedirectUri = postLogoutRedirectUri,
            AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,

            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));
                    Task<AuthenticationResult> result = authContext.AcquireTokenByAuthorizationCodeAsync(
                    code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);

                    return Task.FromResult(0);
                }
            }
        });
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);


        // Uncomment the following lines to enable logging in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");

        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");

        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");

        //app.UseGoogleAuthentication(
        //    clientId: "",
        //    clientSecret: "");
    }
}

EDIT

Here's the controller code

using System.Web.Mvc;

namespace MyApp.Controllers
{

    public class AccueilController : Controller
    {
        [Authorize]
        public ActionResult Index()
        {
            return View();
        }

        [Authorize(Roles = "Admin")]
        public ActionResult List()
        {
            return View();
        }
    }
}

I'm logged in with Cookie Authenticatoin (email/password).

  1. I hit the Index action, I see the content of that page inside my app.
  2. If I hit the List() action, I'm redirected to OpenIdConnect login page.

Solution

  • The webforms user doesn't have the Admin role, but your AD user seems to have the Admin role. You can verify this by quickly querying the AspNetUserRoles table.

    Decorating an action with [Authorize(Roles="Admin")] will make it accessible only to users with the role. The default behaviour for unauthorized users (irrespective of whether the user's authentication status) is to redirect them to the login page.

    Your options are:

    1. Subclass the AuthorizeAttribute and override the default behavior.
    2. Redirect the user somewhere else (perhaps a forbidden page with the right status code set) from the login page if he's authenticated.

    As far as I know, the default redirect behavior cannot be overriden without subclassing the AuthorizeAttribute like you can in ASP.NET Core.