A Complete Full-Stack Project Tutorial
Using Angular + ASP.NET Core + SQL Server
This tutorial covers everything needed to build a real authentication system used in modern web applications.
What You Will Build
A full end-to-end system including:
User Registration
User Login
JWT Authentication
Password Hashing
Protected API Endpoints
Angular AuthService for token handling
Angular Login + Register UI
Route Guards to protect pages
Auto-attach token to HTTP requests
Redirect if user not authenticated
This is the foundation of almost every real web application.
Project Breakdown
We will structure this tutorial into 3 main parts:
Backend: ASP.NET Core Web API
Frontend: Angular Application
Connecting the two
Let's begin.
PART 1: BACKEND (ASP.NET Core Web API)
Step 1: Create a new Web API project
dotnet new webapi -n AuthAPI
cd AuthAPI
Step 2: Install Required Packages
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package BCrypt.Net-Next
Step 3: Create SQL Server Database
CREATE DATABASE AuthDB;
Step 4: Create User Model
Create Models/User.cs:
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public string PasswordHash { get; set; }
}
We store password hashes, not plain text.
Step 5: Add DbContext
File: Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
Step 6: Configure SQL Server
In appsettings.json:
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=AuthDB;Trusted_Connection=True;"
}
Step 7: Register DbContext
In Program.cs:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
Step 8: Create JWT Service
File: Services/JwtService.cs
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using System.Text;
public class JwtService
{
private readonly IConfiguration _config;
public JwtService(IConfiguration config)
{
_config = config;
}
public string GenerateToken(string email)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.Email, email)
};
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddHours(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
Step 9: Add JWT Config in appsettings.json
"Jwt": {
"Key": "VerySecretKey123456789",
"Issuer": "AuthAPI",
"Audience": "AuthAPIUser"
}
Step 10: Configure JWT Middleware
In Program.cs:
var key = Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key)
};
});
builder.Services.AddScoped<JwtService>();
Middleware:
app.UseAuthentication();
app.UseAuthorization();
Step 11: Create Auth Controller
File: Controllers/AuthController.cs
Registration Endpoint
[HttpPost("register")]
public async Task<IActionResult> Register(UserDto request)
{
if (_context.Users.Any(x => x.Email == request.Email))
return BadRequest(new { Message = "Email already exists" });
var user = new User
{
FullName = request.FullName,
Email = request.Email,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password)
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return Ok(new { Message = "User Registered Successfully" });
}
Login Endpoint
[HttpPost("login")]
public IActionResult Login(LoginDto request)
{
var user = _context.Users.FirstOrDefault(x => x.Email == request.Email);
if (user == null)
return Unauthorized(new { Message = "Invalid credentials" });
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
return Unauthorized(new { Message = "Invalid credentials" });
var token = _jwt.GenerateToken(user.Email);
return Ok(new { Token = token });
}
Protected Endpoint
[Authorize]
[HttpGet("profile")]
public IActionResult Profile()
{
return Ok(new { Message = "Access granted to protected data" });
}
Step 12: Create DTOs
File: Models/UserDto.cs
public class UserDto
{
public string FullName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
File: Models/LoginDto.cs
public class LoginDto
{
public string Email { get; set; }
public string Password { get; set; }
}
Step 13: Migrate Database
dotnet ef migrations add InitialCreate
dotnet ef database update
Backend is ready.
PART 2: FRONTEND (Angular)
Step 1: Create Angular Project
ng new auth-app
cd auth-app
Step 2: Install Required Packages
npm install jwt-decode
Step 3: Enable HttpClientModule
app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
BrowserModule,
HttpClientModule
]
})
export class AppModule { }
Step 4: Create AuthService
auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import jwtDecode from 'jwt-decode';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private apiUrl = "https://localhost:5001/api/auth";
constructor(private http: HttpClient) {}
register(data: any) {
return this.http.post(`${this.apiUrl}/register`, data);
}
login(data: any) {
return this.http.post<any>(`${this.apiUrl}/login`, data);
}
saveToken(token: string) {
localStorage.setItem('token', token);
}
getToken() {
return localStorage.getItem('token');
}
isLoggedIn(): boolean {
const token = this.getToken();
if (!token) return false;
const decoded: any = jwtDecode(token);
return Date.now() < decoded.exp * 1000;
}
logout() {
localStorage.removeItem('token');
}
}
Step 5: Auth Interceptor
Attach token automatically.
ng generate interceptor auth
auth.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = this.auth.getToken();
if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
}
return next.handle(req);
}
}
Enable in app.module.ts:
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
Step 6: Create Register Component
ng generate component register
register.component.html
<h2>Register</h2>
<form (ngSubmit)="submit()">
<input type="text" [(ngModel)]="model.fullName" name="fullName" placeholder="Full Name">
<input type="email" [(ngModel)]="model.email" name="email" placeholder="Email">
<input type="password" [(ngModel)]="model.password" name="password" placeholder="Password">
<button>Register</button>
</form>
<p>{{ message }}</p>
register.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-register',
templateUrl: './register.component.html'
})
export class RegisterComponent {
model = { fullName: '', email: '', password: '' };
message = '';
constructor(private auth: AuthService) {}
submit() {
this.auth.register(this.model).subscribe({
next: (res: any) => this.message = res.message,
error: err => this.message = err.error.message
});
}
}
Step 7: Create Login Component
ng generate component login
login.component.html
<h2>Login</h2>
<form (ngSubmit)="submit()">
<input type="email" [(ngModel)]="model.email" name="email" placeholder="Email">
<input type="password" [(ngModel)]="model.password" name="password" placeholder="Password">
<button>Login</button>
</form>
<p>{{ message }}</p>
login.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html'
})
export class LoginComponent {
model = { email: '', password: '' };
message = '';
constructor(private auth: AuthService) {}
submit() {
this.auth.login(this.model).subscribe({
next: (res) => {
this.auth.saveToken(res.token);
this.message = "Login successful";
},
error: err => this.message = "Invalid credentials"
});
}
}
Step 8: Create Dashboard Component (Protected)
ng generate component dashboard
dashboard.component.ts
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
@Component({
templateUrl: './dashboard.component.html'
})
export class DashboardComponent {
message = "";
constructor(private http: HttpClient) {}
ngOnInit() {
this.http.get("https://localhost:5001/api/auth/profile")
.subscribe(res => this.message = "Protected Data Loaded");
}
}
Step 9: Auth Guard
ng generate guard auth
auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate() {
if (this.auth.isLoggedIn()) return true;
this.router.navigate(['/login']);
return false;
}
}
Step 10: Setup Routing
app-routing.module.ts
const routes: Routes = [
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard]
},
{ path: '', redirectTo: '/login', pathMatch: 'full' }
];
PART 3: RUN & TEST
Step 1: Run backend
dotnet run
Step 2: Run Angular
ng serve
Test the Flow
Go to /register
Register a new user
Go to /login
Login with your new account
Token stored in localStorage
Visit /dashboard
Only allowed if logged in
Try deleting token manually → auto redirects to login
Everything works end-to-end.
Conclusion
You now have a fully working authentication system with:
This is a real-world, production-ready foundation.