In this article, we continue developing the blog-type application, focusing on implementing authentication and authorization. We will use:
-
ASP.NET Core Identity for user management
-
JWT (JSON Web Tokens) for stateless authentication
-
React Context + localStorage to maintain authentication on the frontend
-
Protecting routes and API calls based on authentication
🧩 Why JWT? Why stateless?
JWT-based authentication is stateless, which means the server does not need to store sessions. The signed token is sufficient to prove the user's identity. Advantages:
-
Increased scalability
-
Ideal for SPA applications (React, Angular)
-
Easy to extend with role/permission-based authorization
1️⃣ Identity + JWT in .NET Core
✅ Package installation
Add to the WebAPI project:
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
✅ Configuration in Program.cs
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "ludo.blog",
ValidAudience = "ludo.blog",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key_123456"))
};
});
🔐 Make sure the JWT secret is saved in appsettings.json or in UserSecrets.
✅ Token generation
private string GenerateJwtToken(ApplicationUser user)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.NameIdentifier, user.Id)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.Secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _jwtConfig.Issuer,
audience: _jwtConfig.Audience,
claims: claims,
expires: DateTime.Now.AddHours(2),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
2️⃣ Login in React + Token storage
✅ API call from React
const login = async (email: string, password: string) => {
try {
const res = await axios.post("/api/auth/login", { email, password });
localStorage.setItem("token", res.data.token);
setUser(jwtDecode(res.data.token)); // decode token & extract user info
} catch (err) {
setError("Login failed!");
}
};
✅ AuthContext
export const AuthContext = createContext<AuthState>(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
const token = localStorage.getItem("token");
if (token) setUser(jwtDecode(token));
}, []);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
3️⃣ Protecting routes in React
✅ PrivateRoute
const PrivateRoute = ({ children }) => {
const { user } = useAuth();
return user ? children : <Navigate to="/login" />;
};
✅ Usage in App.tsx
<Routes>
<Route path="/posts" element={<PrivateRoute><UserPosts /></PrivateRoute>} />
<Route path="/login" element={<LoginPage />} />
</Routes>
4️⃣ Displaying personal posts
✅ Backend: Secured endpoint
[Authorize]
[HttpGet("me")]
public async Task<IActionResult> GetMyPosts()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var posts = await _postService.GetPostsByUser(userId);
return Ok(posts);
}
✅ Frontend: Call with token
const token = localStorage.getItem("token");
const res = await axios.get("/api/posts/me", {
headers: { Authorization: `Bearer ${token}` }
});
✅ Conclusion
We have implemented a complete authentication and authorization flow for our blog application. The JWT token allows secure communication between frontend and