Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="AuthorizationSample.csproj" />
</Solution>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;

namespace AuthorizationSample.Controllers;

[Route("[controller]/[action]")]
public class AccountController : Controller
{
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

return RedirectToPage("/Account/SignedOut");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace AuthorizationSample.Data;

public class ApplicationUser
{
public string? Email { get; set; }
public string? FullName { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Microsoft.AspNetCore.Mvc;

public static class UrlHelperExtensions
{
public static string GetLocalUrl(this IUrlHelper urlHelper, string localUrl)
{
if (!urlHelper.IsLocalUrl(localUrl))
{
return urlHelper.Page("/Index")!;
}

return localUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@page
@model LoginModel
@{
ViewData["Title"] = "Log in";
}

<h1>@ViewData["Title"]</h1>
<div class="row">
<form method="post">
<h2>Use a local account to log in.</h2>
<hr>
<div asp-validation-summary="All"></div>
<div>
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email">
<span asp-validation-for="Input.Email"></span>
</div>
<div>
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password">
<span asp-validation-for="Input.Password"></span>
</div>
<div>
<button type="submit">Log in</button>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using AuthorizationSample.Data;

namespace AuthorizationSample.Pages.Account
{
public class LoginModel : PageModel
{
[BindProperty]
public InputModel? Input { get; set; }

public string? ReturnUrl { get; private set; }

[TempData]
public string? ErrorMessage { get; set; }

public class InputModel
{
[Required]
[EmailAddress]
public string? Email { get; set; }

[Required]
[DataType(DataType.Password)]
public string? Password { get; set; }
}

public async Task OnGetAsync(string? returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}

// Clear the existing external cookie
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

ReturnUrl = returnUrl;
}

public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
{
ReturnUrl = returnUrl;

if (ModelState.IsValid)
{
var user = await AuthenticateUser(Input?.Email!, Input?.Password!);

if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}

var claims = new List<Claim>
{
new(ClaimTypes.Name, user!.Email!),
new("FullName", user!.FullName!)
};

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

await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));

return LocalRedirect(Url.GetLocalUrl(returnUrl!));
}

// Something failed. Redisplay the form.
return Page();
}

private static async Task<ApplicationUser?> AuthenticateUser(string email, string password)
{
// For demonstration purposes, authenticate a user
// with a static email address. Ignore the password.
// Assume that checking the database takes 500ms

await Task.Delay(500);

if (string.Equals(email, "maria.rodriguez@contoso.com", StringComparison.OrdinalIgnoreCase))
{
return new ApplicationUser()
{
Email = "maria.rodriguez@contoso.com",
FullName = "Maria Rodriguez"
};
}
else
{
return null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@page
@model SignedOutModel
@{
ViewData["Title"] = "Signed out";
}

<h1>@ViewData["Title"]</h1>
<p>
You have successfully signed out.
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationSample.Pages.Account
{
public class SignedOutModel : PageModel
{
public IActionResult OnGet()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToPage("/Index");
}

return Page();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@using AuthorizationSample.Pages.Account
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationSample.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}

@if (User?.Identity?.IsAuthenticated == true)
{
<div class="row">
<div>Hello @User.Claims.FirstOrDefault(c => c.Type == "FullName")?.Value!</div>
<p>Username: @User.Identity.Name</p>
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationSample.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private
@model PrivatePage1Model
@{
ViewData["Title"] = "Private Folder: Private Page 1";
}

<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>

<p>
This page requires authorization by convention:
<code>
options.Conventions.AuthorizeFolder("/Private");
</code>
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationSample.Pages.Private
{
public class PrivatePage1Model : PageModel
{
public string Message { get; private set; }

public void OnGet()
{
Message = "A private page inside the Private folder.";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private
@model PrivatePage2Model
@{
ViewData["Title"] = "Private Folder: Private Page 2";
}

<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>

<p>
This page requires authorization by convention:
<code>
options.Conventions.AuthorizeFolder("/Private");
</code>
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationSample.Pages.Private
{
public class PrivatePage2Model : PageModel
{
public string Message { get; private set; }

public void OnGet()
{
Message = "A private page inside the Private folder.";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private
@model PublicPageModel
@{
ViewData["Title"] = "Private Folder: Public Page";
}

<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>

<p>
The <i>Private</i> folder requires authorization, but this page allows anonymous visitors by convention:
<code>
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
</code>
</p>
Loading