
Ever logged into Netflix and saw “Someone is watching on another device”? Or checked your Google account and found a list of all devices where you’re signed in? That’s exactly what we’re going to build today.
Imagine this scenario:
Ahmed logs into your app from his phone. Later, he logs in from his laptop at work. Then from his tablet at home. One day, he loses his phone. Now he’s worried - someone might access his account!
With a basic authentication system, Ahmed has no way to:
This is a real security problem.
Modern users expect to:
| Action | Example |
|---|---|
| See all active sessions | “You’re logged in on 3 devices” |
| Identify each device | “Chrome on Windows - Last active 2 hours ago” |
| Remove suspicious sessions | “Log out” button next to each device |
| Log out everywhere at once | “Log out from all devices” |
Without this, your app feels incomplete and insecure.
The fix is simple in concept: track every login as a separate session.
Instead of just checking “is this user logged in?”, we ask “is this user logged in on this specific device?”
Normal Login:User → Login → Get Token → Done ❌ (No tracking)Our Approach:User → Login → Create Session Record → Get Token with Session ID → Done ✅
Each session record contains:
First, we need a place to store session information:
public class UserSession{public Guid Id { get; set; }public string UserId { get; set; }public string DeviceName { get; set; } // "Chrome on Windows"public string IpAddress { get; set; }public DateTime CreatedAt { get; set; }public DateTime LastActivityAt { get; set; }public DateTime ExpiresAt { get; set; }public bool IsRevoked { get; set; } // true = logged out}
Think of this like a visitor log at a building entrance. Every time someone enters, we write down who, when, and from where.
When a user logs in, create a session record:
[HttpPost("login")]public async Task<IActionResult> Login(LoginRequest request){// 1. Verify username and password (normal login)var user = await _userManager.FindByEmailAsync(request.Email);if (user == null || !await _userManager.CheckPasswordAsync(user, request.Password))return Unauthorized("Invalid credentials");// 2. Create a session record (NEW!)var session = new UserSession{Id = Guid.NewGuid(),UserId = user.Id,DeviceName = GetDeviceName(Request.Headers["User-Agent"]),IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(),CreatedAt = DateTime.UtcNow,LastActivityAt = DateTime.UtcNow,ExpiresAt = DateTime.UtcNow.AddDays(30),IsRevoked = false};await _db.UserSessions.AddAsync(session);await _db.SaveChangesAsync();// 3. Include session ID in the tokenvar token = GenerateToken(user, session.Id); // session.Id is important!return Ok(new { token });}
The key point: We include session.Id in the JWT token. This links every request to a specific session.
Your token should contain the session ID:
private string GenerateToken(User user, Guid sessionId){var claims = new[]{new Claim(ClaimTypes.NameIdentifier, user.Id),new Claim(ClaimTypes.Email, user.Email),new Claim("session_id", sessionId.ToString()) // This is the magic!};// ... rest of token generation}
Now every request carries information about which session it belongs to.
Here’s where the security happens. Create middleware that checks if the session is still valid:
public class SessionValidationMiddleware{private readonly RequestDelegate _next;public SessionValidationMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context, AppDbContext db){// Only check authenticated requestsif (context.User.Identity?.IsAuthenticated == true){var sessionId = context.User.FindFirst("session_id")?.Value;if (!string.IsNullOrEmpty(sessionId)){var session = await db.UserSessions.FirstOrDefaultAsync(s => s.Id.ToString() == sessionId);// Check if session is revoked or expiredif (session == null || session.IsRevoked || session.ExpiresAt < DateTime.UtcNow){context.Response.StatusCode = 401;await context.Response.WriteAsJsonAsync(new{error = "Session expired or revoked. Please login again."});return;}// Update last activitysession.LastActivityAt = DateTime.UtcNow;await db.SaveChangesAsync();}}await _next(context);}}
What this does:
Create an endpoint to list all active sessions:
[HttpGet("sessions")][Authorize]public async Task<IActionResult> GetMySessions(){var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;var currentSessionId = User.FindFirst("session_id")?.Value;var sessions = await _db.UserSessions.Where(s => s.UserId == userId && !s.IsRevoked && s.ExpiresAt > DateTime.UtcNow).Select(s => new{s.Id,s.DeviceName,s.IpAddress,s.LastActivityAt,IsCurrent = s.Id.ToString() == currentSessionId // Mark current device}).OrderByDescending(s => s.LastActivityAt).ToListAsync();return Ok(sessions);}
Response Example:
[{"id": "abc-123","deviceName": "Chrome on Windows","ipAddress": "192.168.1.10","lastActivityAt": "2024-01-15T14:30:00Z","isCurrent": true},{"id": "def-456","deviceName": "Safari on iPhone","ipAddress": "10.0.0.5","lastActivityAt": "2024-01-15T09:00:00Z","isCurrent": false}]
Now Ahmed can see all his logged-in devices!
Allow users to log out from specific devices:
// Log out from a specific device[HttpDelete("sessions/{sessionId}")][Authorize]public async Task<IActionResult> RevokeSession(Guid sessionId){var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;var session = await _db.UserSessions.FirstOrDefaultAsync(s => s.Id == sessionId && s.UserId == userId);if (session == null)return NotFound("Session not found");session.IsRevoked = true; // Mark as logged outawait _db.SaveChangesAsync();return Ok("Device logged out successfully");}// Log out from ALL other devices[HttpDelete("sessions/others")][Authorize]public async Task<IActionResult> RevokeOtherSessions(){var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;var currentSessionId = User.FindFirst("session_id")?.Value;await _db.UserSessions.Where(s => s.UserId == userId && s.Id.ToString() != currentSessionId).ExecuteUpdateAsync(s => s.SetProperty(x => x.IsRevoked, true));return Ok("All other devices logged out");}
Now Ahmed can log out his lost phone without affecting his current session!
Let’s see the full flow:
Before (without session management):
After (with session management):
✓ Chrome on Windows (This device) - Active now✓ Safari on iPhone - Last active 2 hours ago [Log Out]
var builder = WebApplication.CreateBuilder(args);builder.Services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(connectionString));var app = builder.Build();app.UseAuthentication();app.UseAuthorization();app.UseMiddleware<SessionValidationMiddleware>(); // Add this!app.MapControllers();app.Run();
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/login | Login (creates session) |
| POST | /api/auth/logout | Logout current device |
| GET | /api/sessions | List all active sessions |
| DELETE | /api/sessions/{id} | Logout specific device |
| DELETE | /api/sessions/others | Logout all other devices |
| Problem | Solution |
|---|---|
| User can’t see logged-in devices | GET /sessions returns all active sessions |
| User can’t logout lost device | DELETE /sessions/{id} revokes that session |
| User worried about account security | “Logout all devices” option available |
| No activity tracking | LastActivityAt shows when each device was last used |
| Sessions never expire | ExpiresAt automatically invalidates old sessions |
Make device names more readable using the User-Agent header:
private string GetDeviceName(string userAgent){var browser = "Unknown Browser";var os = "Unknown OS";// Detect browserif (userAgent.Contains("Chrome")) browser = "Chrome";else if (userAgent.Contains("Firefox")) browser = "Firefox";else if (userAgent.Contains("Safari")) browser = "Safari";else if (userAgent.Contains("Edge")) browser = "Edge";// Detect OSif (userAgent.Contains("Windows")) os = "Windows";else if (userAgent.Contains("Mac")) os = "macOS";else if (userAgent.Contains("iPhone")) os = "iPhone";else if (userAgent.Contains("Android")) os = "Android";else if (userAgent.Contains("Linux")) os = "Linux";return $"{browser} on {os}";}
For better detection, use the UAParser NuGet package:
dotnet add package UAParser
With just a few additions to your authentication system, you can give your users:
The key concept is simple: track each login separately, and let users manage them.
Your users will thank you for it! 🙏
Happy coding! 🚀
Your insights drive us! For any questions, feedback, or thoughts, feel free to connect:
If you found this guide beneficial, don’t hesitate to share it with your network. Until the next guide, happy coding!
Quick Links
Legal Stuff




