ASP.NET Core 3.1 - Terms of Service Middleware

This article describes how to implement middleware which will redirect authenticated users to a Terms of Service page until they agree. I will assume you have created a new ASP.NET Core 3.1 Razor Pages project with Individual User Accounts. See Tutorial: Get started with Razor Pages in ASP.NET Core .

This article is part of the Bootstrap Native Project series.

The Bootstrap Native Project (BSNP) is deployed to preview. kenhaggerty. com. The BSNP implements Bootstrap Native and the scaffolded ASP.NET Core Identity UI with user and page enhancements. I encourage you to evaluate Account Management: Sign In Services, 2FA, and Personal Data. If you encounter an issue, I will probably be notified but please send me details in an email.

Access to the BSNP source code may be purchased on KenHaggerty.Com at Manage > Assets.

I enjoy writing these articles. It often enhances and clarifies my coding. The research project is a result of a lot of refactoring and hopefully provides logical segues for the articles. Thank you for supporting my efforts.

While a Terms of Service (TOS) agreement is not a legal requirement, it helps your users understand your rules, requirements, and restrictions. It also helps you enforce these stipulations and maintain control over your website or app. There are plenty of examples and generators for terms of service text. You can require a user to agree with your TOS during registration.

Register TOS Desktop.
Register TOS Mobile.

You should store the date/time of consent in case you update the TOS and want your users to agree with the new terms. The Bootstrap Native Project (BSNP) implements ASP.NET Core Identity which supports user claims. I developed middleware which captures the current request path then redirects the user to a TOS page if the user doesn't have a TOS claim. The TOS page displays an agree button if the user's TOS claim is not found. When the user clicks the agree button, I add the TOS claim with the current date/time and redirect to the original path.

public class TermsOfServiceMiddleware
    private readonly RequestDelegate _next;
    public TermsOfServiceMiddleware(RequestDelegate next)
        _next = next;

    public async Task InvokeAsync(HttpContext context)
        if (context.User.Identity.IsAuthenticated &&
            context.Request.Path != new PathString("/identity/account/termsofservice") &&
            context.Request.Path != new PathString("/identity/account/logout") &&
            !((ClaimsIdentity)context.User.Identity).HasClaim(c => c.Type == ApplicationClaimType.TermsOfService))
            var returnUrl = context.Request.Path.Value == "/" ? "" : "?returnUrl=" +
            context.Response.Redirect("/identity/account/termsofservice" + returnUrl);
        await _next(context).ConfigureAwait(true);

public static class TermsOfServiceMiddlewareExtensions
    public static IApplicationBuilder UseTermsOfService(this IApplicationBuilder builder)
        return builder.UseMiddleware<TermsOfServiceMiddleware>();
TOS Top Desktop.
TOS Top Mobile.

Notice the hint to scroll to the bottom for the agree button.

TOS Agree Desktop.
TOS Agree Mobile.
public string ReturnUrl { get; set; }
public bool ShowAgreeButton { get; set; }

public IActionResult OnGet(string returnUrl = null)
    returnUrl ??= Url.Content("~/");
    ReturnUrl = returnUrl;

    if(_signInManager.IsSignedIn(User) && !User.HasClaim(c => c.Type == ApplicationClaimType.TermsOfService))
        ShowAgreeButton = true;

    return Page();

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    if (!ModelState.IsValid) return Page();

    var user = await _userManager.GetUserAsync(User).ConfigureAwait(false);
    if (user == null) return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
    var claims = User.Claims.ToList();
    if (claims.Count == 0 || claims.Where(c => c.Type == ApplicationClaimType.TermsOfService).FirstOrDefault() == null)
        var result = await _userManager.AddClaimAsync(user,
            new Claim(ApplicationClaimType.TermsOfService,
            string.Format("{0:MM/dd/yyyy}", DateTimeOffset.UtcNow))).ConfigureAwait(false);

        if (!result.Succeeded)
            throw new InvalidOperationException($"Error occurred setting TermsOfService claim " +
                $"({result}) for user with ID '{_userManager.GetUserId(User)}'.");

        await _signInManager.RefreshSignInAsync(user).ConfigureAwait(false);

    returnUrl ??= Url.Content("~/");
    return LocalRedirect(returnUrl);
Ken Haggerty
Created 08/04/20
Updated 08/04/20 02:54 GMT

Log In or Reset Quota to read more.

Article Tags:

Authorization Claims
Successfully completed. Thank you for contributing.
Contribute to enjoy content without advertisments.
Something went wrong. Please try again.



Not accepting new comments.

Submit your comment. Comments are moderated.

User Image.
DisplayedName - Member Since ?