In this tutorial we will learn about cookie authentication without ASP.NET Core Identity. First, we create an empty ASP.NET application with a razor page.
Project
- Open the Visual Studio.
- Create web project using ASP.NET Core Empty template.
- Project name: CookieAuthDemo
- Solution name: CookieAuthDemo
- .NET Core version: 5.0
- Add the AddRazorPages() into the IServiceCollection in Startup class.
- Use MapRazorPages() as terminal middleware in Startup class.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace CascadingDropdownsRP
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}
Add Pages folder in the root directory of the application and create a razor page named Index in it.
Write some HTML tags for 'Hello Cookie' text in the Index page and run the application we get the Hello Cookie printed in the browser. Again add another folder named as Admin in the Pages folder. And inside this folder create another Index page, write some HTML tag in this page also and run the application. We reach to the Index page of Admin folder when we use the URL pointing to the Admin folder. Till now there is no restriction to use the Admin folder and its pages. Any user can access pages of this folder.
Restrict Access of folder
To restrict the access of this folder we use RazorPageOptions as given in the following code. The RazorPageOptions provides options to authorize a razor page or folder or any folder in areas.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(opt =>
{
opt.Conventions.AuthorizeFolder("/admin");
});
}
Since folder authorization is set in the Startup class in AddRazorPages but not middleware is added to support the authorization, we get error. To overcome this exception, we add the UseAuthorization middleware.
Run the application.We get the following exception.
Now add the following line of code in ConfigureServices in Startup class which implies that authentication service is added in the app which is based on cookie authentication. But it is not enough because it just tells about authentication scheme which can be cookie or token etc. We must add cookie and its description. We will get error.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
We still get exception because we must add cookie and set its options, if needed.
In the following line of code, just cookie is added using AddCookie method.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
Login form. Next task is to create a login form in the Login.cshtml razor page.
@page
@model CookieAuthDemo.Pages.Account.LoginModel
<h1>Login here</h1>
<form method="post">
<table>
<tr>
<td><label>Name</label></td>
<td><input type="text" /></td>
</tr>
<tr>
<td><label>Password</label></td>
<td><input type="password" /></td>
</tr>
<tr>
<td colspan="2" style="text-align:right"> <input type="submit" /></td>
</tr>
</table>
</form>
<br />
@ViewData["Message"]
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace CookieAuthDemo.Pages.Account
{
public class LoginModel : PageModel
{
public IActionResult OnPost(string returnUrl)
{
return LocalRedirect(returnUrl);
}
}
}
action="/Pages/Account/Login.cshtml.cs"
Run the application. We get the following error.
namespace CookieAuthDemo.Models
{
public class UserSignIn
{
public string Name { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
}
Update the Login page. Create controls by using tag helpers.
@page
@model CookieAuthDemo.Pages.Account.LoginModel
@using CookieAuthDemo.Models;
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Login here</h1>
<form method="post">
<table>
<tr>
<td><label asp-for="UserSignIn.Name"></label></td>
<td><input type="text" asp-for="UserSignIn.Name" /></td>
</tr>
<tr>
<td><label asp-for="UserSignIn.Password" ></label></td>
<td><input type="password" asp-for="UserSignIn.Password" /></td>
</tr>
<tr>
<td colspan="2" style="text-align:right"> <input type="submit" /></td>
</tr>
<tr>
<td colspan="2"><input type="hidden" asp-for="UserSignIn.Role" value="@ViewData["role"]" /></td>
</tr>
</table>
</form>
<br />
Click Handler. Update the backing Login Razor page. When user submits the form, OnPost event handler will execute. All the sign-in information is stored in claims identity and is tied with default cookie authentication scheme. Finally, this claims identity is encapsulated inside claims principal. The SignInAsync method takes two parameters, first is authentication scheme and second is principal. SignInAsync creates an encrypted cookie and adds it to the current response. If AuthenticationScheme isn't specified, the default scheme is used.
using CookieAuthDemo.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
namespace CookieAuthDemo.Pages.Account
{
public class LoginModel : PageModel
{
[BindProperty]
public UserSignIn UserSignIn { get; set; }
public async Task<IActionResult> OnPostAsync(string returnUrl)
{
// let username and password matches
// check it here
// then we create claims principal
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Name, UserSignIn.Name));
claims.Add(new Claim("Password", UserSignIn.Password));
claims.Add(new Claim(ClaimTypes.Role, UserSignIn.Role));
var ci = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principle = new ClaimsPrincipal(ci);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principle);
return LocalRedirect(returnUrl);
}
public void OnGet()
{
ViewData["role"] = "admin";
}
}
}
Startup class
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace CookieAuthDemo
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(opt =>
{
opt.Conventions.AuthorizeFolder("/admin");
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme
).AddCookie();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
}
@page
@model CookieAuthDemo.Pages.Admin.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>You are on Index page of Admin</h1>
<h3>Is user authenticated: @User.Identity.IsAuthenticated</h3>
<h3>User name: @User.Identity.Name</h3>
<h1><a asp-page-handler="Signout">Signout</a></h1>
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;
namespace CookieAuthDemo.Pages.Admin
{
public class IndexModel : PageModel
{
public async Task<IActionResult> OnGetSignoutAsync()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToPage("/Index");
}
}
}
@page
@model CookieAuthDemo.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<div style="background-color:cyan;width:40%;margin-left:10px">
<h3 style="text-align:right;margin-right:10px">Username: @User.Identity.Name</h3>
<h1>Index of Root page</h1>
<h3><a asp-page="Admin/Index">Go to Index page of Admin</a></h3>
<br/>
<br/>
</div>
- Call UseAuthentication and UseAuthorization to set the HttpContext.User property.
- AuthenticationScheme passed to AddAuthentication sets the default authentication scheme for the app. AuthenticationScheme is useful when there are multiple instances of cookie authentication and the app needs to authorize with a specific scheme. Setting the AuthenticationScheme to CookieAuthenticationDefaults.AuthenticationScheme provides a value of "Cookies" for the scheme. Any string value can be used that distinguishes the scheme.
- The CookieAuthenticationOptions class is used to configure the authentication provider options. Configure CookieAuthenticationOptions in the AddCookie method:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/";
});
- Use CookiePolicyOptions provided to the Cookie Policy Middleware to control global characteristics of cookie processing and hook into cookie processing handlers when cookies are appended or deleted.
- To create a cookie holding user information, construct a ClaimsPrincipal. The user information is serialized and stored in the cookie.
- Create a ClaimsIdentity with any required Claims and call SignInAsync to sign in the user.
- SignInAsync creates an encrypted cookie and adds it to the current response. If AuthenticationScheme isn't specified, the default scheme is used.
Sign out
To sign out the current user and delete their cookie, call SignOutAsync:
// Clear the existing external cookie
await HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
NOTE: If CookieAuthenticationDefaults.AuthenticationScheme or "Cookies" isn't used as the scheme, supply the scheme used when configuring the authentication provider.
// using Microsoft.AspNetCore.Authentication;
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
});
No comments:
Post a Comment