aspnetcore/security/authorization/secure-data.md
By Rick Anderson and Joe Audette
:::moniker range=">= aspnetcore-6.0"
This tutorial shows how to create an ASP.NET Core web app with user data protected by authorization. It displays a list of contacts that authenticated (registered) users have created. There are three security groups:
The images in this document don't exactly match the latest templates.
In the following image, user Rick ([email protected]) is signed in. Rick can only view approved contacts and Edit/Delete/Create New links for his contacts. Only the last record, created by Rick, displays Edit and Delete links. Other users won't see the last record until a manager or administrator changes the status to "Approved".
In the following image, [email protected] is signed in and in the manager's role:
The following image shows the managers details view of a contact:
The Approve and Reject buttons are only displayed for managers and administrators.
In the following image, [email protected] is signed in and in the administrator's role:
The administrator has all privileges. She can read, edit, or delete any contact and change the status of contacts.
The app was created by scaffolding the following Contact model:
The sample contains the following authorization handlers:
ContactIsOwnerAuthorizationHandler: Ensures that a user can only edit their data.ContactManagerAuthorizationHandler: Allows managers to approve or reject contacts.ContactAdministratorsAuthorizationHandler: Allows administrators to approve or reject contacts and to edit/delete contacts.This tutorial is advanced. You should be familiar with:
Download the completed app. Test the completed app so you become familiar with its security features.
[!TIP] Use
git sparse-checkoutto only download the sample subfolder. For example:
git clone --depth 1 --filter=blob:none https://github.com/dotnet/AspNetCore.Docs.git --sparse
cd AspNetCore.Docs
git sparse-checkout init --cone
git sparse-checkout set aspnetcore/security/authorization/secure-data/samples
Run the app, tap the ContactManager link, and verify you can create, edit, and delete a contact. To create the starter app, see Create the starter app.
The following sections have all the major steps to create the secure user data app. You may find it helpful to refer to the completed project.
Use the ASP.NET Identity user ID to ensure users can edit their data, but not other users data. Add OwnerID and ContactStatus to the Contact model:
OwnerID is the user's ID from the AspNetUser table in the Identity database. The Status field determines if a contact is viewable by general users.
Create a new migration and update the database:
dotnet ef migrations add userID_Status
dotnet ef database update
Append xref:Microsoft.AspNetCore.Identity.IdentityBuilder.AddRoles%2A to add Role services:
<a name="rau"></a>
Set the fallback authorization policy to require users to be authenticated:
The preceding highlighted code sets the fallback authorization policy. The fallback authorization policy requires all users to be authenticated, except for Razor Pages, controllers, or action methods with an authorization attribute. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authorization attribute rather than the fallback authorization policy.
xref:Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder.RequireAuthenticatedUser%2A adds xref:Microsoft.AspNetCore.Authorization.Infrastructure.DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.
The fallback authorization policy:
Setting the fallback authorization policy to require users to be authenticated protects newly added Razor Pages and controllers. Having authorization required by default is more secure than relying on new controllers and Razor Pages to include the [Authorize] attribute.
The xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions class also contains xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions.DefaultPolicy?displayProperty=nameWithType. The DefaultPolicy is the policy used with the [Authorize] attribute when no policy is specified. [Authorize] doesn't contain a named policy, unlike [Authorize(PolicyName="MyPolicy")].
For more information on policies, see xref:security/authorization/policies.
An alternative way for MVC controllers and Razor Pages to require all users be authenticated is adding an authorization filter:
The preceding code uses an authorization filter, setting the fallback policy uses endpoint routing. Setting the fallback policy is the preferred way to require all users be authenticated.
Add AllowAnonymous to the Index and Privacy pages so anonymous users can get information about the site before they register:
The SeedData class creates two accounts: administrator and manager. Use the Secret Manager tool to set a password for these accounts. Set the password from the project directory (the directory containing Program.cs):
dotnet user-secrets set SeedUserPW <PW>
If a weak password is specified, an exception is thrown when SeedData.Initialize is called.
Update the app to use the test password:
Update the Initialize method in the SeedData class to create the test accounts:
Add the administrator user ID and ContactStatus to the contacts. Make one of the contacts "Submitted" and one "Rejected". Add the user ID and status to all the contacts. Only one contact is shown:
Create a ContactIsOwnerAuthorizationHandler class in the Authorization folder. The ContactIsOwnerAuthorizationHandler verifies that the user acting on a resource owns the resource.
The ContactIsOwnerAuthorizationHandler calls context.Succeed if the current authenticated user is the contact owner. Authorization handlers generally:
context.Succeed when the requirements are met.Task.CompletedTask when requirements aren't met. Returning Task.CompletedTask without a prior call to context.Success or context.Fail, is not a success or failure, it allows other authorization handlers to run.If you need to explicitly fail, call context.Fail.
The app allows contact owners to edit/delete/create their own data. ContactIsOwnerAuthorizationHandler doesn't need to check the operation passed in the requirement parameter.
Create a ContactManagerAuthorizationHandler class in the Authorization folder. The ContactManagerAuthorizationHandler verifies the user acting on the resource is a manager. Only managers can approve or reject content changes (new or changed).
Create a ContactAdministratorsAuthorizationHandler class in the Authorization folder. The ContactAdministratorsAuthorizationHandler verifies the user acting on the resource is an administrator. Administrator can do all operations.
Services using Entity Framework Core must be registered for dependency injection using xref:Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddScoped%2A. The ContactIsOwnerAuthorizationHandler uses ASP.NET Core Identity, which is built on Entity Framework Core. Register the handlers with the service collection so they're available to the ContactsController through dependency injection. Add the following code to the end of ConfigureServices:
ContactAdministratorsAuthorizationHandler and ContactManagerAuthorizationHandler are added as singletons. They're singletons because they don't use EF and all the information needed is in the Context parameter of the HandleRequirementAsync method.
In this section, you update the Razor Pages and add an operations requirements class.
Review the ContactOperations class. This class contains the requirements the app supports:
Create a base class that contains the services used in the contacts Razor Pages. The base class puts the initialization code in one location:
The preceding code:
IAuthorizationService service to access to the authorization handlers.UserManager service.ApplicationDbContext.Update the create page model:
DI_BasePageModel base class.OnPostAsync method to:
Contact model.Update the OnGetAsync method so only approved contacts are shown to general users:
Add an authorization handler to verify the user owns the contact. Because resource authorization is being validated, the [Authorize] attribute is not enough. The app doesn't have access to the resource when attributes are evaluated. Resource-based authorization must be imperative. Checks must be performed once the app has access to the resource, either by loading it in the page model or by loading it within the handler itself. You frequently access the resource by passing in the resource key.
Update the delete page model to use the authorization handler to verify the user has delete permission on the contact.
Currently, the UI shows edit and delete links for contacts the user can't modify.
Inject the authorization service in the Pages/_ViewImports.cshtml file so it's available to all views:
The preceding markup adds several using statements.
Update the Edit and Delete links in Pages/Contacts/Index.cshtml so they're only rendered for users with the appropriate permissions:
[!WARNING] Hiding links from users that don't have permission to change data doesn't secure the app. Hiding links makes the app more user-friendly by displaying only valid links. Users can hack the generated URLs to invoke edit and delete operations on data they don't own. The Razor Page or controller must enforce access checks to secure the data.
Update the details view so managers can approve or reject contacts:
See this issue for information on:
<a name="challenge"></a>
This app sets the default policy to require authenticated users. The following code allows anonymous users. Anonymous users are allowed to show the differences between Challenge vs Forbid.
In the preceding code:
ChallengeResult is returned. When a ChallengeResult is returned, the user is redirected to the sign-in page.ForbidResult is returned. When a ForbidResult is returned, the user is redirected to the access denied page.[!WARNING] This article uses the Secret Manager tool to store the password for the seeded user accounts. The Secret Manager tool is used to store sensitive data during local development. For information on authentication procedures that can be used when an app is deployed to a test or production environment, see Secure authentication flows.
If you haven't already set a password for seeded user accounts, use the Secret Manager tool to set a password:
Choose a strong password:
Execute the following command from the project's folder, where <PW> is the password:
dotnet user-secrets set SeedUserPW <PW>
If the app has contacts:
Contact table.An easy way to test the completed app is to launch three different browsers (or incognito/InPrivate sessions). In one browser, register a new user (for example, [email protected]). Sign in to each browser with a different user. Verify the following operations:
Details view shows Approve and Reject buttons.| User | Approve or reject contacts | Options |
|---|---|---|
| [email protected] | No | Edit and delete their data. |
| [email protected] | Yes | Edit and delete their data. |
| [email protected] | Yes | Edit and delete all data. |
Create a contact in the administrator's browser. Copy the URL for delete and edit from the administrator contact. Paste these links into the test user's browser to verify the test user can't perform these operations.
Create a Razor Pages app named "ContactManager"
-uld specifies LocalDB instead of SQLitedotnet new webapp -o ContactManager -au Individual -uld
Add Models/Contact.cs:
secure-data\samples\starter6\ContactManager\Models\Contact.cs
[!code-csharp]
Scaffold the Contact model.
Create initial migration and update the database:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet-aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update
Update the ContactManager anchor in the Pages/Shared/_Layout.cshtml file:
<a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>
Test the app by creating, editing, and deleting a contact
Add the SeedData class to the Data folder:
Call SeedData.Initialize from Program.cs:
Test that the app seeded the database. If there are any rows in the contact DB, the seed method doesn't run.
:::moniker-end
:::moniker range="< aspnetcore-6.0"
This tutorial shows how to create an ASP.NET Core web app with user data protected by authorization. It displays a list of contacts that authenticated (registered) users have created. There are three security groups:
The images in this document don't exactly match the latest templates.
In the following image, user Rick ([email protected]) is signed in. Rick can only view approved contacts and Edit/Delete/Create New links for his contacts. Only the last record, created by Rick, displays Edit and Delete links. Other users won't see the last record until a manager or administrator changes the status to "Approved".
In the following image, [email protected] is signed in and in the manager's role:
The following image shows the managers details view of a contact:
The Approve and Reject buttons are only displayed for managers and administrators.
In the following image, [email protected] is signed in and in the administrator's role:
The administrator has all privileges. She can read/edit/delete any contact and change the status of contacts.
The app was created by scaffolding the following Contact model:
The sample contains the following authorization handlers:
ContactIsOwnerAuthorizationHandler: Ensures that a user can only edit their data.ContactManagerAuthorizationHandler: Allows managers to approve or reject contacts.ContactAdministratorsAuthorizationHandler: Allows administrators to:
This tutorial is advanced. You should be familiar with:
Download the completed app. Test the completed app so you become familiar with its security features.
Run the app, tap the ContactManager link, and verify you can create, edit, and delete a contact. To create the starter app, see Create the starter app.
The following sections have all the major steps to create the secure user data app. You may find it helpful to refer to the completed project.
Use the ASP.NET Identity user ID to ensure users can edit their data, but not other users data. Add OwnerID and ContactStatus to the Contact model:
OwnerID is the user's ID from the AspNetUser table in the Identity database. The Status field determines if a contact is viewable by general users.
Create a new migration and update the database:
dotnet ef migrations add userID_Status
dotnet ef database update
Append xref:Microsoft.AspNetCore.Identity.IdentityBuilder.AddRoles%2A to add Role services:
<a name="rau"></a>
Set the fallback authentication policy to require users to be authenticated:
The preceding highlighted code sets the fallback authentication policy. The fallback authentication policy requires all users to be authenticated, except for Razor Pages, controllers, or action methods with an authentication attribute. For example, Razor Pages, controllers, or action methods with [AllowAnonymous] or [Authorize(PolicyName="MyPolicy")] use the applied authentication attribute rather than the fallback authentication policy.
xref:Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder.RequireAuthenticatedUser%2A adds xref:Microsoft.AspNetCore.Authorization.Infrastructure.DenyAnonymousAuthorizationRequirement to the current instance, which enforces that the current user is authenticated.
The fallback authentication policy:
Setting the fallback authentication policy to require users to be authenticated protects newly added Razor Pages and controllers. Having authentication required by default is more secure than relying on new controllers and Razor Pages to include the [Authorize] attribute.
The xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions class also contains xref:Microsoft.AspNetCore.Authorization.AuthorizationOptions.DefaultPolicy?displayProperty=nameWithType. The DefaultPolicy is the policy used with the [Authorize] attribute when no policy is specified. [Authorize] doesn't contain a named policy, unlike [Authorize(PolicyName="MyPolicy")].
For more information on policies, see xref:security/authorization/policies.
An alternative way for MVC controllers and Razor Pages to require all users be authenticated is adding an authorization filter:
The preceding code uses an authorization filter, setting the fallback policy uses endpoint routing. Setting the fallback policy is the preferred way to require all users be authenticated.
Add AllowAnonymous to the Index and Privacy pages so anonymous users can get information about the site before they register:
The SeedData class creates two accounts: administrator and manager. Use the Secret Manager tool to set a password for these accounts. Set the password from the project directory (the directory containing Program.cs):
dotnet user-secrets set SeedUserPW <PW>
If a strong password is not specified, an exception is thrown when SeedData.Initialize is called.
Update Main to use the test password:
Update the Initialize method in the SeedData class to create the test accounts:
Add the administrator user ID and ContactStatus to the contacts. Make one of the contacts "Submitted" and one "Rejected". Add the user ID and status to all the contacts. Only one contact is shown:
Create a ContactIsOwnerAuthorizationHandler class in the Authorization folder. The ContactIsOwnerAuthorizationHandler verifies that the user acting on a resource owns the resource.
The ContactIsOwnerAuthorizationHandler calls context.Succeed if the current authenticated user is the contact owner. Authorization handlers generally:
context.Succeed when the requirements are met.Task.CompletedTask when requirements aren't met. Returning Task.CompletedTask without a prior call to context.Success or context.Fail, is not a success or failure, it allows other authorization handlers to run.If you need to explicitly fail, call context.Fail.
The app allows contact owners to edit/delete/create their own data. ContactIsOwnerAuthorizationHandler doesn't need to check the operation passed in the requirement parameter.
Create a ContactManagerAuthorizationHandler class in the Authorization folder. The ContactManagerAuthorizationHandler verifies the user acting on the resource is a manager. Only managers can approve or reject content changes (new or changed).
Create a ContactAdministratorsAuthorizationHandler class in the Authorization folder. The ContactAdministratorsAuthorizationHandler verifies the user acting on the resource is an administrator. Administrator can do all operations.
Services using Entity Framework Core must be registered for dependency injection using xref:Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddScoped%2A. The ContactIsOwnerAuthorizationHandler uses ASP.NET Core Identity, which is built on Entity Framework Core. Register the handlers with the service collection so they're available to the ContactsController through dependency injection. Add the following code to the end of ConfigureServices:
ContactAdministratorsAuthorizationHandler and ContactManagerAuthorizationHandler are added as singletons. They're singletons because they don't use EF and all the information needed is in the Context parameter of the HandleRequirementAsync method.
In this section, you update the Razor Pages and add an operations requirements class.
Review the ContactOperations class. This class contains the requirements the app supports:
Create a base class that contains the services used in the contacts Razor Pages. The base class puts the initialization code in one location:
The preceding code:
IAuthorizationService service to access to the authorization handlers.UserManager service.ApplicationDbContext.Update the create page model constructor to use the DI_BasePageModel base class:
Update the CreateModel.OnPostAsync method to:
Contact model.Update the OnGetAsync method so only approved contacts are shown to general users:
Add an authorization handler to verify the user owns the contact. Because resource authorization is being validated, the [Authorize] attribute is not enough. The app doesn't have access to the resource when attributes are evaluated. Resource-based authorization must be imperative. Checks must be performed once the app has access to the resource, either by loading it in the page model or by loading it within the handler itself. You frequently access the resource by passing in the resource key.
Update the delete page model to use the authorization handler to verify the user has delete permission on the contact.
Currently, the UI shows edit and delete links for contacts the user can't modify.
Inject the authorization service in the Pages/_ViewImports.cshtml file so it's available to all views:
The preceding markup adds several using statements.
Update the Edit and Delete links in Pages/Contacts/Index.cshtml so they're only rendered for users with the appropriate permissions:
[!WARNING] Hiding links from users that don't have permission to change data doesn't secure the app. Hiding links makes the app more user-friendly by displaying only valid links. Users can hack the generated URLs to invoke edit and delete operations on data they don't own. The Razor Page or controller must enforce access checks to secure the data.
Update the details view so managers can approve or reject contacts:
Update the details page model:
See this issue for information on:
<a name="challenge"></a>
This app sets the default policy to require authenticated users. The following code allows anonymous users. Anonymous users are allowed to show the differences between Challenge vs Forbid.
In the preceding code:
ChallengeResult is returned. When a ChallengeResult is returned, the user is redirected to the sign-in page.ForbidResult is returned. When a ForbidResult is returned, the user is redirected to the access denied page.If you haven't already set a password for seeded user accounts, use the Secret Manager tool to set a password:
Choose a strong password: Use eight or more characters and at least one upper-case character, number, and symbol. For example, Passw0rd! meets the strong password requirements.
Execute the following command from the project's folder, where <PW> is the password:
dotnet user-secrets set SeedUserPW <PW>
If the app has contacts:
Contact table.An easy way to test the completed app is to launch three different browsers (or incognito/InPrivate sessions). In one browser, register a new user (for example, [email protected]). Sign in to each browser with a different user. Verify the following operations:
Details view shows Approve and Reject buttons.| User | Seeded by the app | Options |
|---|---|---|
| [email protected] | No | Edit/delete the own data. |
| [email protected] | Yes | Approve/reject and edit/delete own data. |
| [email protected] | Yes | Approve/reject and edit/delete all data. |
Create a contact in the administrator's browser. Copy the URL for delete and edit from the administrator contact. Paste these links into the test user's browser to verify the test user can't perform these operations.
Create a Razor Pages app named "ContactManager"
-uld specifies LocalDB instead of SQLitedotnet new webapp -o ContactManager -au Individual -uld
Add Models/Contact.cs:
Scaffold the Contact model.
Create initial migration and update the database:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update
If you experience a bug with the dotnet aspnet-codegenerator razorpage command, see this GitHub issue.
Pages/Shared/_Layout.cshtml file:<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
Add the SeedData class to the Data folder:
Call SeedData.Initialize from Main:
Test that the app seeded the database. If there are any rows in the contact DB, the seed method doesn't run.
:::moniker-end
<a name="secure-data-add-resources-label"></a>