Authentication and LoginPath for different areas in ASP.NET Core 2

3.8k views Asked by At

ASP.NET Core 2

Help me to configure AddAuthentication for two routes: users (user accounts) and admin area.

For example, if user doesn't signed in and trying to enter /Account/Orders/ he'll be redirected to /Account/SignIn/.

But if someone trying access /Admin/Orders/ must be redireted to /Admin/Signin/

Have not found ay solution ATM.

2

There are 2 answers

0
Alex On BEST ANSWER

Solved!

In admin area (controllers) we using Authorize attr. arg.: [Authorize(AuthenticationSchemes = "backend")] and that is.

BTW we are able to make any tuning by accessing HttpContext in AddCookie's options and events.

Configuration:

services
    .AddAuthentication(o =>
    {
        o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o =>
    {
        o.LoginPath = new PathString("/account/login/");
    })
    .AddCookie("backend", o =>
    {
        o.LoginPath = new PathString("/admin/account/login/");
    });
0
JJS On

@Alex's answer got me 90% of the way there.

In .Net 6, I'm using this https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0 approach to use Cookies without using the Username setup in identity.

Program.cs

var authentication = services.AddAuthentication(o =>
{
    o.DefaultScheme = AuthenticationSchemes.FrontEnd;
});
authentication.AddCookie(AuthenticationSchemes.FrontEnd, o =>
{
    o.LoginPath = CookieAuthenticationDefaults.LoginPath;
});
authentication.AddCookie(AuthenticationSchemes.BackEnd, o =>
{
    o.LoginPath = new PathString("/admin/login/");
    o.AccessDeniedPath = new PathString("/admin/accessdenied");
});

AppAuthenticationSchemes.cs

public class AuthenticationSchemes
{
    public const string FrontEnd = "Frontend";
    public const string BackEnd = "Backend";
    public const string Either = FrontEnd + "," + BackEnd;
}

AccountController.cs

[AllowAnonymous]
public class AccountController : Controller
{
    private readonly FrontEndSecurityManager _frontEndSecurityManager;

    public AccountController(FrontEndSecurityManager frontEndSecurityManager)
    {
        _frontEndSecurityManager = frontEndSecurityManager;
    }

    [HttpPost(Name = "Login")]
    public async Task<ActionResult> Login(LoginViewModel loginModel)
    {
        if (string.IsNullOrEmpty(loginModel.Username) ||
            string.IsNullOrEmpty(loginModel.Password))
        {
            ModelState.AddModelError("form", "Please enter Username and Password");
            return RedirectToAction("Login", "Account");
        }

        var loginResult = await _frontEndSecurityManager.ValidateCredentials(loginModel.Username, loginModel.Password);
        if (!loginResult.IsSuccess)
        {
            this.AddFlash(FlashMessageType.Danger, "UserName or Password is incorrect");
            return RedirectToAction("Login", "Account");
        }

        var identity = await _frontEndSecurityManager.CreateIdentityAsync(loginModel.Username, loginResult);
        await _frontEndSecurityManager.SignInAsync(identity, HttpContext);

        return RedirectToAction("Menu", "App");
    }
}

FrontEndSecurityManager.cs

public class FrontEndSecurityManager
{
    private readonly SignInApi _api;
    private readonly AuthenticationOptions _authenticationOptions;

    public FrontEndSecurityManager(SignInApi api, IOptions<AuthenticationOptions> authenticationOptions)
    {
        _api = api;
        _authenticationOptions = authenticationOptions.Value;
    }

    public async Task SignInAsync(ClaimsIdentity identity, HttpContext httpContext)
    {
        var authProperties = new AuthenticationProperties
        {
            AllowRefresh = true,
            ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(30),
            IsPersistent = true,
            IssuedUtc = DateTimeOffset.UtcNow
        };

        await httpContext.SignOutAsync(AuthenticationSchemes.BackEnd);
        await httpContext.SignInAsync(AuthenticationSchemes.FrontEnd, new ClaimsPrincipal(identity), authProperties);
    }

    public async Task<LoginResult> ValidateCredentials(string username, string password)
    {
        if (_authenticationOptions.DemoUserEnabled)
        {
            if (string.Equals(username, "demo", StringComparison.InvariantCultureIgnoreCase))
            {
                var result = new LoginResult(StandardResults.SuccessResult, "")
                {
                    Employee_Name = "Demo User",
                    Employee_Email = "[email protected]",
                    Employee_Initials = "DG",
                    Employee_Type = "Regular",
                    Role1 = true,
                    Role2 = true,
                    Role3 = false
                };
                return result;
            }

        }

        var apiRequest = new LoginRequest() { Username = username, Password = password };
        var loginResult = await _api.LoginAsync(apiRequest);
        if (loginResult.Success)
        {
            return loginResult.Data;
        }
        else
        {
            return LoginResult.Failure();   
        }
    }

    public async Task<ClaimsIdentity> CreateIdentityAsync(string username, LoginResult loginResult)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, username),
            new Claim(ClaimTypes.Role, AppRoles.User),
            new Claim(ClaimTypes.Email, loginResult.Employee_Email, ClaimValueTypes.Email),
            new Claim(ClaimTypes.GivenName, loginResult.GivenName),
            new Claim(ClaimTypes.Surname, loginResult.Surname),
            new Claim(AppClaimTypes.EmployeeType, loginResult.Employee_Type),
            new Claim(AppClaimTypes.EmployeeInitials, loginResult.Employee_Initials),
            new Claim(AppClaimTypes.Location, loginResult.Location.ToString(), ClaimValueTypes.Integer),
        };

        if (loginResult.Use_Checkin)
        {
            claims.Add(new Claim(ClaimTypes.Role, AppRoles.Checkin));
        }

        if (loginResult.Use_Pickup)
        {
            claims.Add(new Claim(ClaimTypes.Role, AppRoles.Pickup));
        }

        var identity = new ClaimsIdentity(claims, AuthenticationSchemes.FrontEnd);
        return identity;
    }

    public void SignOutAsync(HttpContext httpContext)
    {
        httpContext.SignOutAsync(AuthenticationSchemes.FrontEnd);
    }
}

From here, you could easily extrapolate how you want the back-end authentication controller to work. Essentially something like

await HttpContext.SignOutAsync(AuthenticationSchemes.FrontEnd);await HttpContext.SignInAsync(AuthenticationSchemes.BackEnd, new ClaimsPrincipal(identity), authProperties);

An example of using each policy would be

[Authorize(AuthenticationSchemes = AuthenticationSchemes.BackEnd)]
public IActionResult Secure()
{
   return View("Secure");
}

or

[Authorize] // scheme not explicit, so Pr DefaultScheme = AuthenticationSchemes.FrontEnd is used
[Route("[controller]")]
public class OutgoingController : Controller
{
}