Auth0 Integration for .NET and Angular
Developer/Agent Implementation Plan: Auth0 Integration for ASP.NET Core API and Angular UI
Section titled “Developer/Agent Implementation Plan: Auth0 Integration for ASP.NET Core API and Angular UI”This plan is written for developers or coding agents implementing Auth0 in a .NET API plus Angular frontend.
Target stack:
Frontend: AngularBackend: ASP.NET Core Minimal APIAuth provider: Auth0Login UX: Auth0 Universal LoginIdentity providers: Google first, Facebook laterAuthorization: Auth0 scopes/permissions + app database ownership checksDatabase: app-owned Users table keyed by Auth0 subject claimObaki API implementation status
Section titled “Obaki API implementation status”The API side is implemented for the current Obaki architecture with these adaptations:
[x] JWT bearer package and central package version added[x] Auth0 options added under `Auth0`[x] Auth0 validation is disabled by default with `Auth0:Enabled=false`[x] When enabled, API validates Auth0 issuer, audience, signing key, and lifetime[x] CORS options added under `Cors`[x] Existing API header security allows browser Bearer-token requests only on configured auth paths and allows CORS preflight[x] `/api/me` creates or refreshes a local app user from Auth0 `sub`[x] `/api/me/token` is available only when development Scalar endpoints are enabled[x] `users` table migration added with unique `auth0_user_id`[x] Authenticated, own-notification, and admin policy names exist[ ] Angular integration is still a separate UI slice[x] Account-owned notification subscription endpoints require authenticated usersCurrent Obaki deviation from this generic plan:
The `/api/notification-subscriptions` endpoints now require authenticated users.Ownership is enforced by the authenticated subject-derived user key, not by trusting the public subscription id alone.Source references
Section titled “Source references”Primary references used while preparing this plan:
- Auth0 ASP.NET Core Web API quickstart: https://auth0.com/docs/quickstart/backend/aspnet-core-webapi
- Auth0 Angular SPA quickstart: https://auth0.com/docs/quickstart/spa/angular
- Auth0 Angular SDK docs: https://auth0.com/docs/libraries/auth0-angular-spa
- Auth0 application settings: https://auth0.com/docs/get-started/applications/application-settings
- Auth0 API settings: https://auth0.com/docs/get-started/apis/api-settings
- Auth0 RBAC: https://auth0.com/docs/manage-users/access-control/configure-core-rbac
- Auth0 access token validation: https://auth0.com/docs/secure/tokens/access-tokens/validate-access-tokens
- Auth0 custom claims: https://auth0.com/docs/secure/tokens/json-web-tokens/create-custom-claims
- Auth0 OIDC scopes: https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes
1. Implementation goals
Section titled “1. Implementation goals”Must have
Section titled “Must have”[ ] Angular can sign in through Auth0 Universal Login[ ] Angular can sign out[ ] Angular can read authenticated UI state[ ] Angular sends Auth0 access token to API[ ] API validates issuer, audience, signature, expiry[ ] API exposes protected endpoints[ ] API can identify current user from `sub`[ ] API creates local user row on first authenticated request[ ] Notification-rule endpoints are protected[ ] Admin-only endpoints are separated from normal user endpointsShould have
Section titled “Should have”[ ] Google login enabled[ ] Facebook login can be enabled without code changes[ ] CORS locked to Angular origin[ ] Secrets are not committed[ ] Production and local Auth0 settings are separated[ ] Basic permission policy exists[ ] Tests cover 401, 403, and authenticated success pathsMust not do
Section titled “Must not do”[ ] Do not build custom password login[ ] Do not store Auth0 client secret in Angular[ ] Do not use email as primary user identity[ ] Do not trust frontend-only authorization[ ] Do not log raw JWTs[ ] Do not accept tokens without validating audience[ ] Do not send ID token to API as the API credential2. Target architecture
Section titled “2. Target architecture”Angular Auth0 Angular SDK AuthGuard HTTP interceptor Access token with API audience | vASP.NET Core API JWT Bearer middleware Auth0 issuer + audience validation Authorization policies CurrentUser service User bootstrap/sync service | vDatabase Users NotificationRules Admin/domain tablesAuth0 owns authentication.
The application owns authorization rules that are specific to the community tool.
Examples:
Auth0 says who the caller is.Your API decides whether that caller can create a notification rule.Your database decides whether that notification rule belongs to that caller.3. Auth0 configuration contract
Section titled “3. Auth0 configuration contract”Before coding, these Auth0 values must exist.
Auth0 SPA application
Section titled “Auth0 SPA application”Application name: Obaki WebApplication type: Single Page ApplicationAllowed Callback URLs: http://localhost:4200 https://YOUR_FRONTEND_DOMAINAllowed Logout URLs: http://localhost:4200 https://YOUR_FRONTEND_DOMAINAllowed Web Origins: http://localhost:4200 https://YOUR_FRONTEND_DOMAINRequired frontend values:
AUTH0_DOMAINAUTH0_CLIENT_IDAUTH0_AUDIENCEAuth0 API
Section titled “Auth0 API”API name: Obaki APIIdentifier/Audience: https://api.jjosh.devSigning algorithm: RS256Required backend values:
Auth0:DomainAuth0:AudienceInitial permissions
Section titled “Initial permissions”Start with:
read:memanage:own_notificationsadmin:manage_notificationsDo not create a huge permission list early.
4. Backend implementation plan: ASP.NET Core API
Section titled “4. Backend implementation plan: ASP.NET Core API”4.1 Add packages
Section titled “4.1 Add packages”Install JWT bearer authentication:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearerOptional if the project uses OpenAPI:
dotnet add package Swashbuckle.AspNetCore4.2 Add configuration
Section titled “4.2 Add configuration”appsettings.Development.json:
{ "Auth0": { "Enabled": true, "Domain": "YOUR_TENANT.REGION.auth0.com", "Audience": "https://api.jjosh.dev", "ClaimNamespace": "https://jjosh.dev/claims" }, "Cors": { "AllowedOrigins": [ "http://localhost:4200" ] }}Production environment variables:
Auth0__Enabled=trueAuth0__Domain=YOUR_TENANT.REGION.auth0.comAuth0__Audience=https://api.jjosh.devAuth0__ClaimNamespace=https://jjosh.dev/claimsCors__AllowedOrigins__0=https://YOUR_FRONTEND_DOMAINDo not commit production secrets or provider client secrets.
The API does not need the Auth0 SPA client secret.
4.3 Create strongly typed options
Section titled “4.3 Create strongly typed options”public sealed class Auth0Options{ public required string Domain { get; init; } public required string Audience { get; init; }}Register:
builder.Services.Configure<Auth0Options>(builder.Configuration.GetSection("Auth0"));4.4 Configure authentication
Section titled “4.4 Configure authentication”Program.cs:
using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
var auth0Domain = builder.Configuration["Auth0:Domain"] ?? throw new InvalidOperationException("Missing Auth0:Domain.");
var auth0Audience = builder.Configuration["Auth0:Audience"] ?? throw new InvalidOperationException("Missing Auth0:Audience.");
builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = $"https://{auth0Domain}/"; options.Audience = auth0Audience; options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, NameClaimType = "sub" }; });
builder.Services.AddAuthorization();Pipeline order:
var app = builder.Build();
app.UseHttpsRedirection();app.UseCors("Frontend");app.UseAuthentication();app.UseAuthorization();
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" }));
app.Run();UseAuthentication() must run before UseAuthorization().
4.5 Configure CORS
Section titled “4.5 Configure CORS”var allowedOrigins = builder.Configuration .GetSection("Cors:AllowedOrigins") .Get<string[]>() ?? [];
builder.Services.AddCors(options =>{ options.AddPolicy("Frontend", policy => { policy .WithOrigins(allowedOrigins) .AllowAnyHeader() .AllowAnyMethod(); });});Do not use AllowAnyOrigin() with authenticated browser APIs.
4.6 Add current user abstraction
Section titled “4.6 Add current user abstraction”using System.Security.Claims;
public interface ICurrentUser{ bool IsAuthenticated { get; } string Auth0UserId { get; }}
public sealed class CurrentUser(IHttpContextAccessor httpContextAccessor) : ICurrentUser{ public bool IsAuthenticated => httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated == true;
public string Auth0UserId { get { var user = httpContextAccessor.HttpContext?.User; var sub = user?.FindFirstValue("sub");
if (string.IsNullOrWhiteSpace(sub)) { throw new UnauthorizedAccessException("Missing Auth0 subject claim."); }
return sub; } }}Register:
builder.Services.AddHttpContextAccessor();builder.Services.AddScoped<ICurrentUser, CurrentUser>();Use sub as the stable external identity.
Do not use email as the identity key.
4.7 Add protected endpoint smoke test
Section titled “4.7 Add protected endpoint smoke test”app.MapGet("/api/me/token", (ClaimsPrincipal user) =>{ return Results.Ok(new { sub = user.FindFirstValue("sub"), claims = user.Claims.Select(c => new { c.Type, c.Value }) });}).RequireAuthorization();Use this only during integration. Remove or restrict it before production if it exposes too much claim data.
4.8 Add local user table
Section titled “4.8 Add local user table”Recommended table:
Users- Id: Guid / long- Auth0UserId: string, unique, required- Email: string?, optional- DisplayName: string?, optional- AvatarUrl: string?, optional- CreatedAtUtc- LastSeenAtUtc- IsDisabledEF Core entity:
public sealed class AppUser{ public Guid Id { get; set; } public required string Auth0UserId { get; set; } public string? Email { get; set; } public string? DisplayName { get; set; } public string? AvatarUrl { get; set; } public DateTimeOffset CreatedAtUtc { get; set; } public DateTimeOffset LastSeenAtUtc { get; set; } public bool IsDisabled { get; set; }}EF configuration:
builder.Entity<AppUser>(entity =>{ entity.HasKey(x => x.Id); entity.Property(x => x.Auth0UserId).HasMaxLength(256).IsRequired(); entity.HasIndex(x => x.Auth0UserId).IsUnique(); entity.Property(x => x.Email).HasMaxLength(320); entity.Property(x => x.DisplayName).HasMaxLength(200); entity.Property(x => x.AvatarUrl).HasMaxLength(1000);});4.9 Bootstrap local user
Section titled “4.9 Bootstrap local user”Create a service that ensures the Auth0 user exists in your database.
public interface IUserBootstrapper{ Task<AppUser> GetOrCreateAsync(ClaimsPrincipal principal, CancellationToken cancellationToken);}Implementation rules:
1. Read sub from access token.2. Find Users.Auth0UserId == sub.3. If found, update LastSeenAtUtc.4. If not found, create row.5. Do not fail if email/name/picture are missing.6. If IsDisabled is true, reject the request.Basic endpoint:
app.MapGet("/api/me", async ( ClaimsPrincipal principal, IUserBootstrapper bootstrapper, CancellationToken cancellationToken) =>{ var user = await bootstrapper.GetOrCreateAsync(principal, cancellationToken);
return Results.Ok(new { user.Id, user.Email, user.DisplayName, user.AvatarUrl });}).RequireAuthorization();4.10 Getting email/name/picture in the API
Section titled “4.10 Getting email/name/picture in the API”Important: Auth0 access tokens for your API may not automatically contain all profile claims.
Preferred learning path:
Use an Auth0 Post Login Action to add small, namespaced custom claims to the access token.Example Auth0 Action:
exports.onExecutePostLogin = async (event, api) => { const namespace = 'https://jjosh.dev/claims';
if (event.authorization) { api.accessToken.setCustomClaim(`${namespace}/email`, event.user.email); api.accessToken.setCustomClaim(`${namespace}/name`, event.user.name); api.accessToken.setCustomClaim(`${namespace}/picture`, event.user.picture); }};Then read them in .NET:
private const string ClaimNamespace = "https://jjosh.dev/claims";
var email = principal.FindFirstValue($"{ClaimNamespace}/email");var name = principal.FindFirstValue($"{ClaimNamespace}/name");var picture = principal.FindFirstValue($"{ClaimNamespace}/picture");Keep custom claims small.
Do not add heavy data to tokens.
4.11 Add scope/permission policy
Section titled “4.11 Add scope/permission policy”Auth0 can put scopes in the scope claim as a space-separated string.
Create helper:
public static class AuthorizationPolicyBuilderExtensions{ public static AuthorizationPolicyBuilder RequireScope( this AuthorizationPolicyBuilder builder, string scope) { return builder.RequireAssertion(context => { var scopeClaim = context.User.FindFirst("scope")?.Value;
if (string.IsNullOrWhiteSpace(scopeClaim)) { return false; }
return scopeClaim.Split(' ', StringSplitOptions.RemoveEmptyEntries) .Contains(scope, StringComparer.Ordinal); }); }}Register policy:
builder.Services.AddAuthorization(options =>{ options.AddPolicy("ManageOwnNotifications", policy => { policy.RequireAuthenticatedUser(); policy.RequireScope("manage:own_notifications"); });});Use policy:
app.MapPost("/api/notification-rules", CreateNotificationRule) .RequireAuthorization("ManageOwnNotifications");If using Auth0 RBAC with permissions in access token, you may also check the permissions claim/array depending on token format and middleware behavior.
4.12 Protect Minimal API route groups
Section titled “4.12 Protect Minimal API route groups”var api = app.MapGroup("/api");
api.MapGet("/public/outages", GetPublicOutages);
var authed = api.MapGroup("").RequireAuthorization();
authed.MapGet("/me", GetMe);authed.MapGet("/notification-rules", GetMyNotificationRules);authed.MapPost("/notification-rules", CreateNotificationRule) .RequireAuthorization("ManageOwnNotifications");
var admin = api.MapGroup("/admin") .RequireAuthorization("AdminOnly");
admin.MapGet("/users", GetUsers);admin.MapPost("/notification-rules/{id:guid}/disable", DisableRule);4.13 Domain authorization rules
Section titled “4.13 Domain authorization rules”Always check ownership in the database.
Bad:
User has manage:own_notifications, so allow editing any notification rule ID.Correct:
User has manage:own_notifications.Rule.UserId == current local user ID.Then allow update.4.14 Disable user flow
Section titled “4.14 Disable user flow”Local app users need a disable flag even if Auth0 user still exists.
Users.IsDisabled = trueMiddleware/service behavior:
If authenticated Auth0 user maps to disabled local user, return 403.Do not delete users immediately. Soft-disable first.
5. Frontend implementation plan: Angular
Section titled “5. Frontend implementation plan: Angular”5.1 Install SDK
Section titled “5.1 Install SDK”npm install @auth0/auth0-angularAuth0’s Angular quickstart states the SDK supports Angular 13 and newer, and the current quickstart covers modern Angular versions.
5.2 Add environment config
Section titled “5.2 Add environment config”src/environments/environment.ts:
export const environment = { production: false, apiBaseUrl: 'https://localhost:5001', auth0: { domain: 'YOUR_TENANT.REGION.auth0.com', clientId: 'YOUR_AUTH0_SPA_CLIENT_ID', audience: 'https://api.jjosh.dev', redirectUri: window.location.origin }};src/environments/environment.prod.ts:
export const environment = { production: true, apiBaseUrl: 'https://api.jjosh.dev', auth0: { domain: 'YOUR_TENANT.REGION.auth0.com', clientId: 'YOUR_AUTH0_SPA_CLIENT_ID', audience: 'https://api.jjosh.dev', redirectUri: window.location.origin }};The Auth0 SPA client ID is public. It can exist in frontend config.
The Auth0 client secret must not exist in frontend config.
5.3 Configure Auth0 provider
Section titled “5.3 Configure Auth0 provider”For standalone Angular app config:
import { ApplicationConfig } from '@angular/core';import { provideRouter } from '@angular/router';import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';import { provideAuth0, AuthHttpInterceptor } from '@auth0/auth0-angular';import { HTTP_INTERCEPTORS } from '@angular/common/http';import { routes } from './app.routes';import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), provideHttpClient(withInterceptorsFromDi()), provideAuth0({ domain: environment.auth0.domain, clientId: environment.auth0.clientId, authorizationParams: { redirect_uri: environment.auth0.redirectUri, audience: environment.auth0.audience }, httpInterceptor: { allowedList: [ `${environment.apiBaseUrl}/api/*` ] } }), { provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true } ]};For NgModule-based Angular, configure AuthModule.forRoot(...) instead.
5.4 Login button
Section titled “5.4 Login button”import { Component, inject } from '@angular/core';import { AuthService } from '@auth0/auth0-angular';
@Component({ selector: 'app-login-button', template: `<button type="button" (click)="login()">Sign in</button>`})export class LoginButtonComponent { private readonly auth = inject(AuthService);
login(): void { this.auth.loginWithRedirect(); }}5.5 Logout button
Section titled “5.5 Logout button”import { Component, inject } from '@angular/core';import { AuthService } from '@auth0/auth0-angular';
@Component({ selector: 'app-logout-button', template: `<button type="button" (click)="logout()">Sign out</button>`})export class LogoutButtonComponent { private readonly auth = inject(AuthService);
logout(): void { this.auth.logout({ logoutParams: { returnTo: window.location.origin } }); }}The returnTo URL must be listed in Auth0 Allowed Logout URLs.
5.6 Show authenticated user state
Section titled “5.6 Show authenticated user state”import { Component, inject } from '@angular/core';import { AsyncPipe, JsonPipe } from '@angular/common';import { AuthService } from '@auth0/auth0-angular';
@Component({ selector: 'app-auth-status', standalone: true, imports: [AsyncPipe, JsonPipe], template: ` @if (auth.isAuthenticated$ | async) { <pre>{{ auth.user$ | async | json }}</pre> } @else { <p>Not signed in.</p> } `})export class AuthStatusComponent { readonly auth = inject(AuthService);}Use this for debugging only. Do not expose raw profile data unnecessarily in production UI.
5.7 Protect routes
Section titled “5.7 Protect routes”import { Routes } from '@angular/router';import { AuthGuard } from '@auth0/auth0-angular';
export const routes: Routes = [ { path: 'notification-rules', canActivate: [AuthGuard], loadComponent: () => import('./features/notification-rules/notification-rules.component') .then(m => m.NotificationRulesComponent) }];Route guards improve UX.
They do not replace API authorization.
5.8 API client
Section titled “5.8 API client”import { HttpClient } from '@angular/common/http';import { inject, Injectable } from '@angular/core';import { environment } from '../../environments/environment';
@Injectable({ providedIn: 'root' })export class MeApiClient { private readonly http = inject(HttpClient);
getMe() { return this.http.get(`${environment.apiBaseUrl}/api/me`); }}The Auth0 HTTP interceptor attaches the access token to URLs matching allowedList.
5.9 Bootstrap app user after login
Section titled “5.9 Bootstrap app user after login”After authentication, call:
GET /api/meThis lets the backend create or update the local Users row.
Angular can do this in an app initializer, shell component, or auth-aware user store.
Simple approach:
this.auth.isAuthenticated$.subscribe(isAuthenticated => { if (isAuthenticated) { this.meApiClient.getMe().subscribe(); }});Production code should manage subscriptions cleanly.
6. Data model plan
Section titled “6. Data model plan”Users- Id- Auth0UserId unique- Email nullable- DisplayName nullable- AvatarUrl nullable- CreatedAtUtc- LastSeenAtUtc- IsDisabledNotificationRules
Section titled “NotificationRules”NotificationRules- Id- UserId- Keyword- NormalizedKeyword- IsEnabled- CreatedAtUtc- UpdatedAtUtcConstraints:
User max active rules: 3-5Keyword min length: 3Keyword max length: 80Unique normalized keyword per user, if desiredOptional audit table
Section titled “Optional audit table”UserLoginEvents- Id- UserId- Auth0UserId- Provider nullable- OccurredAtUtc- IpHash nullable- UserAgentHash nullableDo not store raw IP forever unless you have a clear privacy reason.
7. Authorization model
Section titled “7. Authorization model”API-level policies
Section titled “API-level policies”AuthenticatedUser- requires valid JWT
ManageOwnNotifications- requires valid JWT- requires manage:own_notifications scope/permission
AdminOnly- requires valid JWT- requires admin:manage_notifications scope/permission or local admin flagDomain-level rules
Section titled “Domain-level rules”A user can read only their own notification rules.A user can create only up to the configured max active rules.A user can update/delete only rules owned by them.An admin can disable abusive rules.Keep Auth0 authorization coarse.
Keep resource ownership in your database.
8. Recommended feature slices
Section titled “8. Recommended feature slices”Example backend structure:
Modules/Auth/ CurrentUser.cs UserBootstrapper.cs AuthPolicies.cs ClaimsExtensions.cs
Modules/Users/ AppUser.cs GetMeEndpoint.cs
Modules/NotificationRules/ NotificationRule.cs GetMyNotificationRulesEndpoint.cs CreateNotificationRuleEndpoint.cs UpdateNotificationRuleEndpoint.cs DeleteNotificationRuleEndpoint.csExample Angular structure:
src/app/core/auth/ auth.config.ts auth-buttons.component.ts user-session.store.ts
src/app/core/api/ me-api.client.ts notification-rules-api.client.ts
src/app/features/notification-rules/ notification-rules.page.ts notification-rule-form.component.ts9. Testing plan
Section titled “9. Testing plan”Backend integration tests
Section titled “Backend integration tests”Use fake/test authentication for most API tests.
Do not call Auth0 in normal test runs.
Test cases:
GET /api/me without token returns 401GET /api/me with valid test principal returns 200POST /api/notification-rules without token returns 401POST /api/notification-rules without required scope returns 403POST /api/notification-rules with required scope creates ruleUser cannot update another user's ruleDisabled local user gets 403JWT validation smoke test
Section titled “JWT validation smoke test”Have one manual test that uses a real Auth0 token against local API.
Do not make CI depend on Auth0 unless specifically building an external integration pipeline.
Angular tests
Section titled “Angular tests”Test:
Login button calls loginWithRedirectLogout button calls logoutProtected route uses AuthGuardAPI client calls correct endpointAuth interceptor is configured for API base URL10. Manual end-to-end test script
Section titled “10. Manual end-to-end test script”1. Start API on https://localhost:50012. Start Angular on http://localhost:42003. Click Sign in4. Complete Auth0 login5. Return to Angular6. Open browser dev tools7. Call /api/me through UI8. Confirm Authorization: Bearer token is attached9. Confirm API returns current app user10. Create notification rule11. Refresh page12. Confirm session persists13. Sign out14. Confirm API calls stop being authorizedProduction
Section titled “Production”1. Deploy API2. Deploy Angular3. Add production frontend URL to Auth0 Allowed Callback URLs4. Add production frontend URL to Auth0 Allowed Logout URLs5. Add production frontend URL to Auth0 Allowed Web Origins6. Add production frontend origin to API CORS7. Login with Google8. Login with Facebook, if enabled9. Confirm /api/me works10. Confirm notification-rule endpoints enforce ownership11. Common failure modes
Section titled “11. Common failure modes”401 from API
Section titled “401 from API”Likely causes:
Audience mismatchIssuer/domain mismatchAngular did not request audienceToken is expiredUsing ID token instead of access tokenMissing Authorization headerAPI is running HTTP while frontend expects HTTPS token/cookie behaviorCheck:
Auth0 API Identifier == Angular audience == API AudienceAPI Authority == https://YOUR_AUTH0_DOMAIN/CORS error
Section titled “CORS error”Likely causes:
Angular origin missing from API CORSAPI CORS middleware order wrongAuth0 Allowed Web Origins missing Angular originUsing trailing slash mismatch in some settingsUse exact origins:
http://localhost:4200https://app.jjosh.devNot:
http://localhost:4200/https://app.jjosh.dev/Login redirect error
Section titled “Login redirect error”Likely causes:
Angular redirect_uri not listed in Auth0 Allowed Callback URLsProduction URL missingWrong callback routeLogout redirect error
Section titled “Logout redirect error”Likely cause:
returnTo URL is not listed in Allowed Logout URLsMissing email/name in backend
Section titled “Missing email/name in backend”Likely cause:
Access token does not include profile claimsFix:
Use Auth0 Action to add small namespaced custom claims, or let Angular show profile from ID token/userinfo while backend only uses sub.12. Security checklist
Section titled “12. Security checklist”[ ] API validates issuer[ ] API validates audience[ ] API validates token lifetime[ ] API validates signing key[ ] API requires HTTPS in production[ ] API CORS allows only known frontend origins[ ] Auth0 callback URLs are exact[ ] Auth0 developer keys are not used in production social connections[ ] Angular does not contain client secret[ ] Backend logs do not include tokens[ ] Local user identity uses Auth0 sub, not email[ ] Admin actions require server-side policy[ ] User-owned resources check database ownership[ ] Disabled local users are rejected[ ] Rate limits exist on write endpoints[ ] Suspicious community-generated data can be disabled by admin13. Agent implementation order
Section titled “13. Agent implementation order”Use this sequence. Do not skip ahead.
Phase 1: Auth0 tenant/app/API config
Section titled “Phase 1: Auth0 tenant/app/API config”1. Confirm Auth0 domain, client ID, and API audience.2. Confirm Angular callback/logout/web origins.3. Confirm API identifier/audience.4. Confirm Google connection is enabled.Deliverable:
A short AUTH0_SETUP.md file in the repo with non-secret config values and dashboard checklist.Phase 2: API JWT validation
Section titled “Phase 2: API JWT validation”1. Add JWT bearer package.2. Add Auth0 config binding.3. Configure authentication/authorization middleware.4. Add /api/me/token smoke endpoint.5. Verify 401 without token.6. Verify 200 with real Auth0 token.Deliverable:
Protected API endpoint works with Auth0 access token.Phase 3: Angular Auth0 integration
Section titled “Phase 3: Angular Auth0 integration”1. Install @auth0/auth0-angular.2. Add environment config.3. Register Auth0 provider.4. Add login/logout buttons.5. Add protected route.6. Add HTTP interceptor allowedList.7. Call /api/me/token from Angular.Deliverable:
Angular signs in and calls protected API successfully.Phase 4: Local user bootstrap
Section titled “Phase 4: Local user bootstrap”1. Add Users table/entity.2. Add unique index on Auth0UserId.3. Add CurrentUser service.4. Add UserBootstrapper.5. Replace /api/me/token with production /api/me.6. Create local user on first authenticated request.Deliverable:
Authenticated users have local app user records.Phase 5: Notification rules authorization
Section titled “Phase 5: Notification rules authorization”1. Protect notification rule endpoints.2. Add ownership checks.3. Add max active rule limit.4. Add keyword validation.5. Add admin disable endpoint.Deliverable:
Users can manage only their own rules; admin can disable abusive rules.Phase 6: RBAC/permissions
Section titled “Phase 6: RBAC/permissions”1. Enable RBAC for Auth0 API if needed.2. Add API permissions.3. Create roles.4. Assign test user role.5. Validate scopes/permissions in API policies.Deliverable:
Policy-based authorization works for normal user and admin paths.Phase 7: Facebook connection
Section titled “Phase 7: Facebook connection”1. Create Meta app.2. Configure Facebook Login.3. Add Auth0 callback URL to Meta.4. Add Facebook App ID/Secret to Auth0 connection.5. Enable Facebook connection for Angular app.6. Test with Meta test user first.7. Move to live mode when public requirements are satisfied.Deliverable:
Facebook sign-in works through the same Angular/Auth0/API path.14. Definition of done
Section titled “14. Definition of done”The integration is done when:
[ ] A user can login with Google from Angular[ ] Angular receives authenticated state[ ] Angular can call /api/me[ ] API validates JWT using Auth0 issuer/audience[ ] API creates or updates local user row[ ] API rejects unauthenticated calls with 401[ ] API rejects unauthorized calls with 403[ ] User cannot access another user's rules[ ] API CORS is locked to frontend origin[ ] Production Auth0 URLs are configured[ ] Secrets are not in the repo[ ] Setup documentation exists in the repo15. Final implementation rule
Section titled “15. Final implementation rule”Keep authentication boring.
Use Auth0 for login.
Use ASP.NET Core authorization policies for API gates.
Use your database for ownership and community-abuse rules.
Do not let OAuth complexity leak into the rest of the application.
