본문 바로가기
ASP.NET Core

ASP.NET Core] Cookie Authentication을 이용한 로그인

by Fastlane 2023. 5. 17.
728x90
반응형

1. AddAuthentication

Cookie Authentication을 사용하기 위해서 아래와 같이 서비스에 등록한다. 

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<AppDbContext>(option => option.UseSqlServer(
    builder.Configuration.GetConnectionString("DefaultConnection")
    ));

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
        options.SlidingExpiration = true;
        options.AccessDeniedPath = "/Forbidden/";
    });

해당 케이스는 하나의 Scheme을 등록했기 때문에 상관없지만, 여러개를 등록한 경우 Authorize attribute에서 AuthenticationSchemes argument에 name을 명시해주어야 한다. 명시해주지 않은 경우 default name("Cookies")을 사용한다. 

[Authorize(AuthenticationSchemes ="Cookies")]

 2. Authentication Middleware

middleware도 등록한다. 

app.UseAuthentication();
app.UseAuthorization();

3. UserLoginModel

using System.ComponentModel.DataAnnotations;

namespace TEST.Models
{
    public class UserLoginModel
    {
        [Required]
        public string Id { get; set; }
        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}

4. Login Form 

AccountController

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TEST.Models;
using TEST.Services;
using System.Security.Claims;

namespace TEST.Controllers
{
    public class AccountController : Controller
    {
        private readonly IAccountService _accountService;

        public AccountController(IAccountService accountService)
        {
            _accountService = accountService;
        }

        public IActionResult Login(string returnUrl = null)
        {
            ViewData["ReturnUrl"] = returnUrl;
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(UserLoginModel userModel, string? returnUrl = null)
        {
            if (!ModelState.IsValid)
            {
                return View(userModel);
            }

            var account = await _accountService.Login(userModel.Id, userModel.Password);

            if (account == null)
            {
                ViewBag.msg = "invalid account info";
                return View("Login");
            }

            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, account.EmpId),
                new Claim(ClaimTypes.Name, account.EmpName),
                new Claim("CenterCod", account.CenterCod.ToString()),
                new Claim(ClaimTypes.Role, "Administrator"),
            };

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


            var authProperties = new AuthenticationProperties
            {
                //AllowRefresh = <bool>,
                // Refreshing the authentication session should be allowed.

                //ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
                // The time at which the authentication ticket expires. A 
                // value set here overrides the ExpireTimeSpan option of 
                // CookieAuthenticationOptions set with AddCookie.

                //IsPersistent = true,
                // Whether the authentication session is persisted across 
                // multiple requests. When used with cookies, controls
                // whether the cookie's lifetime is absolute (matching the
                // lifetime of the authentication ticket) or session-based.

                //IssuedUtc = <DateTimeOffset>,
                // The time at which the authentication ticket was issued.

                //RedirectUri = <string>
                // The full path or absolute URI to be used as an http 
                // redirect response value.
            };

            //.AspNetCore.Cookies 암호화된 cookie를 브라우저에 생성한다. 
            await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);

            if (!string.IsNullOrEmpty(returnUrl))
            {
                return LocalRedirect(returnUrl);
            }

            return RedirectToAction("Welcome");
        }

        [Authorize]
        public async Task<IActionResult> LogOut()
        {
            // Clear the existing external cookie
            await HttpContext.SignOutAsync(
                CookieAuthenticationDefaults.AuthenticationScheme);

            return Redirect("/");
        }

        [Authorize]
        public IActionResult Welcome()
        {
            return View("Welcome");
        }
    }
}

Login.cshtml

@model UserLoginModel
<h3>Login</h3>
@ViewBag.msg
<form method="post" asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]">
    UserId <input type="text" asp-for="Id" name="Id" />
    <span asp-validation-for="Id" class="text-danger"></span>
    <br />
    Password <input type="password" name="password" />
    <span asp-validation-for="Password" class="text-danger"></span>
    <br />
    <input type="submit" value="Login" />
</form>

_Layout.cshtml

아래 내용 추가 

User의 로그인 여부는 ClaimsPrincipal 객체인 User Property로 알 수 있으며, 자동으로 Controller와 Razor Pages에 주입된다. 

@using System.Security.Claims

@if (User.Identity.IsAuthenticated)
{
        if (@User.Claims.Any(
                x => x.Type == ClaimTypes.Name))
        {
            <li class="nav-item">
                <a class="nav-link text-dark" 
                asp-controller="Account" asp-action="Profile">
                    @User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name).Value
                </a>
            </li>
        }
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="LogOut">LogOut</a>
    </li>
}
else
{
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Login">Login</a>
    </li>                 
}

 

로그인 하지 않은 상태로, /Account/Welcome에 접속을 하면 ASP.NET Core는 자동으로 ReturnUrl query string을 붙여서 로그인 url로 이동시킨다. 

/Account/Login?ReturnUrl=%2FAccount%2FWelcome

로그인 후에, /Account/Welcome으로 자동 redirect된다. 

 

쿠키가 생성되었다. 

 

5. Authorize Attribute Roles Argument

UserController.cs

User Role에만 접속권한을 부여하였다. 

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using TEST.Data;
using TEST.Models;
using TEST.Services;

namespace TEST.Controllers
{
    [Authorize(Roles = "User")]
    public class UserController : Controller
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IRazorRenderService _renderService;
        public UserController(IUnitOfWork unitOfWork, IRazorRenderService renderService)
        {
            _unitOfWork = unitOfWork;
            _renderService = renderService;

        }
        public IActionResult Index()
        {
            return View();
        }
    }
}

로그인 후, 접속 시 Role이 일치하지 않기 때문에 ForbidAsync()함수를 실행하고 404 not found response를 받는다. 

option으로 설정한 /Forbidden/ 경로로 이동한다. 

 

6. IHttpContextAccessor 

Controller Constructor에서 User Property값을 얻기 위해서는 IHttpContextAccessor interface를 주입한다. 

IHttpContextAccessor를 통해서 HttpContext object를 얻을 수 있다. 

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using TEST.Data;
using TEST.Models;
using TEST.ViewModels;
using System.Security.Claims;

namespace TEST.Controllers
{
    [Authorize]
    public class FileController : Controller
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IUnitOfWork _unitOfWork;
        public FileController(IHttpContextAccessor httpContextAccessor, IUnitOfWork unitOfWork)
        {
            _httpContextAccessor = httpContextAccessor;
            _unitOfWork = unitOfWork;
            
            Console.WriteLine("User Id: " + _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier));
        }

        public async Task<IActionResult> Index()
        {
            Console.WriteLine("Role: " + User.FindFirstValue(ClaimTypes.Role));
            return View();
        }

    }
}

Program.cs

builder.Services.AddHttpContextAccessor();

 

728x90
반응형

댓글