5e8050bc28
* initial commit * fix app component * Add readme and code to prever XSRF * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> * Update samples/ngIdentity/README.md Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> --------- Co-authored-by: Rick Anderson <3605364+Rick-Anderson@users.noreply.github.com> |
||
---|---|---|
.. | ||
ngIdentity.Server | ||
ngidentity.client | ||
NuGet.config | ||
README.md |
README.md
Angular client with ASP.NET Core identity APIs
This sample project demonstrates how to use ASP.NET Core Identity APIs with an Angular client. The project was generated using Angular hosted on an ASP.NET Core 8 template, then modified to add identity.
Run the sample
Either run or inspect the launch settings for your projects. "Client" refers to the ngIdentity.client
project and "server" refers to the ngIdentity.server
host project.
- Modify
proxy.conf.js
and ensure the URL and port are correct for your server project. - Modify
home.component.html
to post to the server URL. - Run the project
Sometimes a race condition can occur where the Angular build lags behind the server. The server automatically kicks off an Angular build if it can't connect to the client, so this results in two instances of the Angular build. The second Angular build then fails with a port conflict. When this happens, close the first Angular client to launch and the second will succeed. If you close the second, it will end the debug session.
Understand the sample
The identity APIs were added to the server project, then the client was modified to use the APIs.
Server
The following changes were made to the server project to support authentication and authorization:
- Added the
Microsoft.AspNetCore.Identity.EntityFrameworkCore
NuGet package. - Added the
Microsoft.EntityFrameworkCore.InMemory
NuGet package. You can use any relational EF Core provider, but you will need to properly create/migrate the database (see Migrations Overview). - Added the identity user and database.
class MyUser : IdentityUser { } class AppDbContext(DbContextOptions<AppDbContext> options) : IdentityDbContext<MyUser>(options) { }
- Configured the identity services. The code here changes the default behavior of returning a
404
when not authenticated to a401
so the client can automatically redirect to the login page.builder.Services.AddAuthentication(IdentityConstants.ApplicationScheme) .AddIdentityCookies() .ApplicationCookie!.Configure(opt => opt.Events = new CookieAuthenticationEvents() { OnRedirectToLogin = ctx => { ctx.Response.StatusCode = 401; return Task.CompletedTask; } }); builder.Services.AddAuthorizationBuilder();
- Configured the EF Core
DbContext
to use the in-memory identity database, and turned on the identity APIs.builder.Services.AddDbContext<AppDbContext>( options => options.UseInMemoryDatabase("AppDb")); builder.Services.AddIdentityCore<MyUser>() .AddEntityFrameworkStores<AppDbContext>() .AddApiEndpoints();
- Mapped the identity API endpoints:
app.MapIdentityApi<MyUser>();
- Added a
logout
method. Thelogout
method isn't included in the endpoints because it may change based on the configuration, i.e. cookies, tokens, or both. - Locked down the weather forecast API to only respond to authenticated users by adding the extension RequireAuthorization.
The logout
endpoint is defined like this:
app.MapPost("/logout", async (
SignInManager<MyUser> signInManager,
[FromBody]object empty) =>
{
if (empty is not null)
{
await signInManager.SignOutAsync();
return Results.Ok();
}
return Results.NotFound();
}).RequireAuthorization();
Minimal APIs can bind to forms. When binding to forms, the site is at risk of cross-site request forgery (CSRF) attacks. To prevent CSRF attacks, use the anti-forgery services. This logout doesn't bind to a form, but it also takes no parameters. Without parameters, an empty forms post from a malicious site could invoke the API. To avoid this, the client posts with an empty body: {}
. The server checks for the parameter. If the empty body is included, the empty
parameter is not null
and the method succeeds. The empty
parameter is deserialized as a JObject
.
A forms post can't produce the empty JSON body, so will either render a null parameter or throw a 415
(media type not supported). There is an option on the home page of the client app to try to sign out using the cross-site forms post.
Client
The components to manage authentication are in the identity
folder.
- dto defines the shape of parameters for the identity APIs.
- guard can be used with the router to prevent navigation to a route if the user is not authenticated. It will automatically redirect to the login page. It is used to protect the weather forecast route (although the server will also prevent it from being accessed by an unauthenticated user).
{ path: 'forecast', component: ForecastComponent, canActivate: mapToCanActivate([AuthGuard]) }
- interceptor is a convenience feature that responds to any
401
unauthorized by redirecting to the login page. Note that when using roles or claims for authorization, a user could be logged in and receive a401
when accessing a resource they don't have permissions for. This interceptor shouldn't be used in that scenario. - service is the main service responsible for logging the user in and out and verifying whether the user is authenticated.
The other components use these services to restrict access. The common pattern is to inject AuthService
and check if the user is logged in. The client should also subscribe to onStateChanged
to refresh anytime the user logs out or signs back in.
public isSignedIn: boolean = false;
constructor(private authService: AuthService) { }
ngOnInit(): void {
this.authService.onStateChanged().forEach((state: boolean) => {
this.isSignedIn = state;
});
this.authService.isSignedIn().forEach((signedIn: boolean) => {
this.isSignedIn = signedIn;
});
}
The component uses *ngIf
to selectively render code based on the authentication state.