This repository provides a template for an authentication system built with Angular 21 and .NET 10, using Entity Framework Core, tested against MySQL. It offers a secure foundation for applications requiring user authentication, with options for self-registration and two-factor authentication (2FA).
Starter template, not a finished app — see How to Use This Template to get started.
- Click Use this template on GitHub to generate your own repository from this one — not a fork, a fresh repo with its own history.
- Clone your new repo locally.
- Follow Running the Template As-Is to confirm it works before changing anything.
- Work through Customizing for Your Project to make it yours.
- Frontend: Angular 21
- Backend: .NET 10
- Database: EF Core, tested against MySQL (see Customizing for Your Project for swapping providers)
/api— the .NET backend solution (AngularDotNetAuthTemplate.sln,AngularDotNetAuthTemplate.Api/)/client— the Angular frontend
The app runs as a single process: the API serves the Angular build output directly, so there's nothing to configure for cross-origin requests.
- User login and registration
- Optional self-registration
- Configurable two-factor authentication (2FA) options
- Secure password storage and management
This template is meant to be generated and adapted, not used as-is. Every
spot that needs a look before you ship is marked TODO(template) (in code
comments) or a literal [Application Name] placeholder (in copy that gets
sent to users). Find them all with:
git grep -n "TODO(template)"
git grep -n "\[Application Name\]" -- api client/srcWhat's currently marked:
- App name in emails/SMS/authenticator app —
api/AngularDotNetAuthTemplate.Api/Controllers/API/AccountController.cshas[Application Name]in the confirmation, password reset, and 2FA email/SMS copy, plus the issuer name shown in authenticator apps (GenerateQrCodeUri). The Angular routetitles inclient/src/app/app.routes.tsandclient/src/index.html's default<title>use the same placeholder. - Logo and favicon —
client/src/assets/images/logo-small.png(used across the login/register/2FA pages) andclient/src/favicon.icoare both placeholders; replace the files in place, no code changes needed. - Seeded admin account — off by default; see
SeedAdminin Backend Setup below. client/package.json's"name"— still the Angular CLI default ("angular"); harmless to leave, but worth renaming if you're publishing this as its own project.- Angular feature flags —
is2FaRequired,allowUserEdit,allowSelfRegister,twoFaMethods,requiredProfileFields, andavailableRolesinclient/src/environments/environment.ts/environment.prod.ts— see Options below. LICENSE's copyright holder,CONTRIBUTING.md, anddocs/LOCAL_DEV.md— all still placeholders from the template itself.- JWT config — see JWT Configuration below.
JwtConfigs.securityKey in appsettings.json ships with an obviously-fake
default (...ReplaceMe); replace it with a real secret before any real
deployment, and never commit the real value. validIssuer/validAudience
in the same block are just internal labels the client and server need to
agree on, but worth updating to reflect your actual app/API name too.
Via appsettings.Development.json (gitignored):
{
"JwtConfigs": {
"securityKey": "some-long-random-secret-value",
"validIssuer": "YourAppAPI",
"validAudience": "https://localhost:7249"
}
}Or via environment variables:
export JwtConfigs__securityKey="some-long-random-secret-value"
export JwtConfigs__validIssuer="YourAppAPI"
export JwtConfigs__validAudience="https://localhost:7249"Email and SMS sending are provided by
DGates.Identity.NotificationProviders,
a NuGet package referenced from AngularDotNetAuthTemplate.Api.csproj, not
implemented in this repo. Fixes and new providers land in the package and
reach generated repos via an ordinary package update, not a template
re-sync.
The default registrations in Program.cs are marked TODO(template).
Email defaults to AddSmtpEmailSender (pointed at the Mailpit container so
a fresh clone works with no external account), with
AddSendGridEmailSender/AddPostMarkEmailSender already called but
commented out. SMS defaults to AddTwilioSmsSender, with AddSnsSmsSender
(AWS SNS) already called but commented out. Uncomment the extension method
for the provider you want and supply your own API key/credentials via
appsettings.Development.json or user-secrets to switch providers. Each
alternative provider's BaseUrlOverride/ServiceUrlOverride in
appsettings.json already points at that provider's mock (see Notification
Provider Mocks below), so switching a
provider in Program.cs works against the mock with no further config
changes — only clear the override and supply real credentials once you're
ready to hit the real service. Never commit real provider credentials.
The app only uses EF Core's provider-agnostic APIs — no raw SQL, no
MySQL-specific query syntax anywhere in the codebase. MySQL (via
Pomelo.EntityFrameworkCore.MySql) is the only provider this template
ships with and has been tested against, wired up in the single
options.UseMySql(...) call in Program.cs. Swapping to another EF Core
provider (SQL Server, PostgreSQL, SQLite, etc.) means: referencing that
provider's NuGet package instead of Pomelo's, changing that one UseMySql
call to the provider's equivalent (UseSqlServer, UseNpgsql, etc.), and
regenerating the EF Core migrations from scratch for the new provider —
the ones shipped here are MySQL-specific (see the
MySqlModelBuilderExtensions calls in Migrations/) and won't apply as-is
against a different database.
@fortawesome/angular-fontawesome, angularx-qrcode, and ngx-mask all
release major versions in lockstep with Angular's own major version rather
than independent semver — e.g. ngx-mask@21.x targets Angular 21,
ngx-mask@22.x targets Angular 22. When you run your own ng update in the
future, bump these alongside it. npm install will happily resolve a stale
peer range without complaint; only npm ci (used in CI, see the badge
above) enforces it, so a mismatch here can pass local npm install and only
surface once CI (or a teammate's clean clone) runs npm ci.
The whole sequence, assuming Node/npm, .NET SDK 10, and Docker are already installed, and you've already generated your own repo via Use this template (see How to Use This Template). See the detailed sections below for what each step does and why.
git clone https://github.com/yourusername/your-generated-repo.git
cd your-generated-repo
docker compose up -d mysql mailpit smsmock
cd api
dotnet tool restore
cd AngularDotNetAuthTemplate.Api
dotnet ef database update
cd ../..
cd client
npm install
ng build
cd ..
dotnet run --project api/AngularDotNetAuthTemplate.ApiAvailable at:
- App: https://localhost:7249
- Mailpit (dev inbox — confirmation/reset emails land here instead of a real inbox): http://localhost:8025
- SMS mock (dev inbox for SMS 2FA codes — see below): http://localhost:3030
Follow these steps to set up and run the project locally.
- Node.js and npm for Angular
- .NET SDK 10 for the backend
- Docker for database setup, and optionally for running the whole app (see below)
- MySQL Client (optional, for direct database access)
Once you've generated your own repo from this template (see How to Use This Template), clone it locally:
git clone https://github.com/yourusername/your-generated-repo.git
cd your-generated-repo-
Start MySQL, Mailpit, and the SMS mock (from the repo root —
docker-compose.ymlalso defines anapiservice, but leave it out for now; it needs the database migrated first, see below):docker compose up -d mysql mailpit smsmock
This starts a
mysqlcontainer with the database, user, and password already provisioned to matchappsettings.json'sDefaultConnection, mapped tolocalhost:3307. No manual SQL setup needed.It also starts
mailpit, a local SMTP catcher — the app's defaultSmtpEmailConfigsinappsettings.jsonpoint at it, so registration confirmation, password reset, and other outbound emails during local dev are caught instead of actually sent. View them athttp://localhost:8025. To use a real provider instead, see Notification Senders below.smsmockplays the same role for SMS 2FA: the prebuilttwilio-mockimage implementing the Twilio REST API. The app's defaultTwilioSmsConfigs.BaseUrlOverrideinappsettings.jsonpoints the sender registered viaAddTwilioSmsSenderat it, so 2FA codes sent via SMS are caught instead of going through a real Twilio account — view them athttp://localhost:3030. To use a real Twilio account instead, setTwilioSmsConfigs.AccountSid/AuthToken/FromNumberto real values and clearBaseUrlOverrideviaappsettings.Development.jsonor user-secrets — never commit real credentials. -
Install the EF Core CLI tool (one-time per clone —
Microsoft.EntityFrameworkCore.Toolsin the.csprojonly wires up the Visual Studio Package Manager Console cmdlets; thedotnet efcommand itself comes from a separate tool package, pinned inapi/.config/dotnet-tools.json):cd api dotnet tool restore -
Run Migrations (from
api/AngularDotNetAuthTemplate.Api/—dotnet efresolves the target project from the current directory):cd AngularDotNetAuthTemplate.Api dotnet ef database update -
(Optional) Bootstrap an admin account. There's no seeded user by default. Set
SeedAdmin:EmailandSeedAdmin:Passwordbefore first run, and the app creates that user — pre-confirmed, in theAdminrole — on startup. Safe to leave set across restarts; it only creates the user once.Via
appsettings.Development.json(gitignored):{ "SeedAdmin": { "Email": "admin@example.com", "Password": "ChangeMe123!" } }Or via environment variables:
export SeedAdmin__Email="admin@example.com" export SeedAdmin__Password="ChangeMe123!"
mysql, mailpit, and smsmock (above) back the providers wired up by
default. Everything below except localstack (the official LocalStack
image) is published from
dgates-mock-servers, a
shared repo of GHCR-published mock servers used by both this template and
DGates.Identity.NotificationProviders. docker-compose.yml here defines
mocks for every other provider this template implements, so you can develop
against any of them without a real account — start whichever ones you need
alongside the services above:
docker compose up -d sendgridmock postmarkmock localstacksendgridmock—sendgrid-mock, a SendGrid-compatible REST API.SendGridEmailConfigs.BaseUrlOverridealready points at it (http://localhost:3040) — uncommentAddSendGridEmailSenderinProgram.csto use it. View sent messages athttp://localhost:3040, orcurl http://localhost:3040/api/messages.postmarkmock—postmark-mock, a Postmark-compatible REST API.PostMarkEmailConfigs.BaseUrlOverridealready points at it (http://localhost:3050) — uncommentAddPostMarkEmailSenderinProgram.csto use it. View sent messages athttp://localhost:3050, orcurl http://localhost:3050/api/messages.localstack— the official LocalStack image, running only the SNS service, for AWS SNS SMS sending.SnsSmsConfigs.ServiceUrlOverridealready points at it (http://localhost:4566) with LocalStack's standardtest/testfake credentials — uncommentAddSnsSmsSenderinProgram.csto use it. LocalStack has no web UI for this; view sent messages withcurl http://localhost:4566/_aws/sns/sms-messages(LocalStack's own introspection endpoint — SNS SMS has no real delivery to observe, even against LocalStack).
If you're running the api service itself via Docker Compose (not
dotnet run on the host), the BaseUrlOverride/ServiceUrlOverride values
above won't resolve — localhost inside that container means the container
itself, not a sibling mock container. docker-compose.yml's api service
already overrides each one to the mock's Compose service name
(e.g. http://postmarkmock:3050) so this works out of the box; the
http://localhost:PORT values above are what to use from the host machine
(e.g. from a browser, or dotnet run).
See Notification Senders above for how to swap providers, and never commit real provider credentials.
-
Navigate to the
clientfolder and install dependencies:cd client npm install -
Build the Angular application:
ng build
This outputs to
client/dist/browser, which the API serves as static files.
Open api/AngularDotNetAuthTemplate.sln in your IDE and run the AngularDotNetAuthTemplate.Api project, or from the repo root:
dotnet run --project api/AngularDotNetAuthTemplate.ApiThe Dockerfile builds the Angular client and the API together into a single
image (api/AngularDotNetAuthTemplate.Api/Dockerfile). The easiest way to run
it locally is via the api service already defined in docker-compose.yml —
it shares a Docker network with mysql/mailpit/smsmock, so it can reach
them by service name instead of localhost. Requires the database to already be
migrated (see Backend Setup above) — the container doesn't run migrations
itself:
docker compose up -d --build apiBrowse to http://localhost:8080.
Alternatively, to build/run the image standalone (e.g. to test the raw
production image outside this compose network), run from the repo root,
since the build needs both api/ and client/, and point
ConnectionStrings__DefaultConnection/SmtpEmailConfigs__Host/
TwilioSmsConfigs__BaseUrlOverride at wherever your MySQL/SMTP/SMS mock
actually are — localhost won't resolve to anything inside the container:
docker build -f api/AngularDotNetAuthTemplate.Api/Dockerfile -t angular-dotnet-auth-template .
docker run -p 8080:8080 \
--add-host=host.docker.internal:host-gateway \
-e ConnectionStrings__DefaultConnection="Server=host.docker.internal;Port=3307;Database=AuthTemplate;User=webapp;Password=mypass" \
-e TwilioSmsConfigs__BaseUrlOverride="http://host.docker.internal:3030" \
-e SmtpEmailConfigs__Host=host.docker.internal \
angular-dotnet-auth-templateThe app listens on HTTP only inside the container (port 8080, matching the .NET base image's default).
This template offers several configurable options to customize the authentication flow:
- Allow Self-Registration: Enable or disable user registration.
- Enforce 2FA: Enforce two-factor authentication for enhanced security.
- 2FA Options:
- Authenticator App (e.g., Google Authenticator)
- Email Verification
- SMS Verification
- Required Profile Fields: Choose which fields (name, phone number, mailing address) are mandatory on the register-user, edit-user, and edit-profile forms. Email is always required, since it's the account's login name.
To modify these options, adjust the corresponding settings in the configuration files
(client/src/environments/environment.ts and environment.prod.ts).
Port already in use. A previous run may still be alive in the
background (e.g. a terminal or IDE session that got closed without
stopping the process cleanly) and still holding the port. Find and stop
it: lsof -i :<port> then kill <pid> (Linux/macOS), or on Windows
Get-Process -Id (Get-NetTCPConnection -LocalPort <port>).OwningProcess | Stop-Process.
This project is licensed under the MIT License.