HomeContact

Managing User Sessions Across Multiple Devices in .NET 8

By Shady Nagy
Published in dotnet
January 15, 2026
2 min read
Managing User Sessions Across Multiple Devices in .NET 8

Table Of Contents

01
Introduction
02
The Problem
03
What Users Expect Today
04
The Solution: Session Tracking
05
Let's Build It Step by Step
06
Real-World Example
07
Quick Setup Summary
08
Benefits Summary
09
Bonus: Detect Device Information
10
Conclusion
11
Next Steps
12
Feedback and Questions

Introduction

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.

The Problem

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:

  • See which devices are logged into his account
  • Log out from his lost phone remotely
  • Know if someone else is using his account

This is a real security problem.

What Users Expect Today

Modern users expect to:

ActionExample
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 Solution: Session Tracking

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?”

How It Works

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:

  • Which user it belongs to
  • Device information (browser, OS, device type)
  • When it was created
  • Last activity time
  • Whether it’s been revoked (logged out)

Let’s Build It Step by Step

Step 1: Create the Session Model

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.

Step 2: Create a Session on Login

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 token
var 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.

Step 3: Include Session ID in JWT Token

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.

Step 4: Validate Sessions on Every Request

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 requests
if (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 expired
if (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 activity
session.LastActivityAt = DateTime.UtcNow;
await db.SaveChangesAsync();
}
}
await _next(context);
}
}

What this does:

  • ✅ Valid session → Request continues
  • ❌ Revoked session → Returns 401 (Unauthorized)
  • ❌ Expired session → Returns 401 (Unauthorized)

Step 5: Let Users See Their Sessions

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!

Step 6: Let Users Revoke Sessions

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 out
await _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!

Real-World Example

Let’s see the full flow:

Scenario: Ahmed loses his phone

Before (without session management):

  1. Ahmed loses phone 😰
  2. Ahmed can’t do anything about it
  3. Whoever finds the phone has access to Ahmed’s account 😱

After (with session management):

  1. Ahmed loses phone 😰
  2. Ahmed logs into the website from his laptop
  3. Ahmed goes to “Settings → Active Sessions”
  4. Ahmed sees:
    ✓ Chrome on Windows (This device) - Active now
    ✓ Safari on iPhone - Last active 2 hours ago [Log Out]
  5. Ahmed clicks “Log Out” on the iPhone session
  6. Phone is now logged out remotely 🎉
  7. If someone tries to use the app on that phone, they see “Please login again”

Quick Setup Summary

1. Register services in Program.cs

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();

2. Required endpoints

MethodEndpointDescription
POST/api/auth/loginLogin (creates session)
POST/api/auth/logoutLogout current device
GET/api/sessionsList all active sessions
DELETE/api/sessions/{id}Logout specific device
DELETE/api/sessions/othersLogout all other devices

Benefits Summary

ProblemSolution
User can’t see logged-in devicesGET /sessions returns all active sessions
User can’t logout lost deviceDELETE /sessions/{id} revokes that session
User worried about account security“Logout all devices” option available
No activity trackingLastActivityAt shows when each device was last used
Sessions never expireExpiresAt automatically invalidates old sessions

Bonus: Detect Device Information

Make device names more readable using the User-Agent header:

private string GetDeviceName(string userAgent)
{
var browser = "Unknown Browser";
var os = "Unknown OS";
// Detect browser
if (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 OS
if (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

Conclusion

With just a few additions to your authentication system, you can give your users:

  • ✅ Visibility into their active sessions
  • ✅ Control over their account security
  • ✅ Peace of mind when devices are lost or stolen
  • ✅ A professional, modern authentication experience

The key concept is simple: track each login separately, and let users manage them.

Your users will thank you for it! 🙏

Next Steps

  1. Add email notifications when a new device logs in
  2. Add location detection from IP addresses
  3. Set a maximum number of active sessions per user
  4. Add suspicious login detection (new location/device alerts)

Happy coding! 🚀

Feedback and Questions

Your insights drive us! For any questions, feedback, or thoughts, feel free to connect:

  1. Email: shady@shadynagy.com
  2. Twitter: @ShadyNagy_
  3. LinkedIn: Shady Nagy
  4. GitHub: ShadyNagy

If you found this guide beneficial, don’t hesitate to share it with your network. Until the next guide, happy coding!


Tags

.NET Coredotnet.NET 8

Share


Previous Article
Solving Windows Path Length Limitations in Git
Shady Nagy

Shady Nagy

Software Innovation Architect

Topics

AI
Angular
dotnet
GatsbyJS
Github
Linux
MS SQL
Oracle

Related Posts

Why Your EF Core Tests Are Lying to You (And How SQLite In-Memory Fixes It)
Why Your EF Core Tests Are Lying to You (And How SQLite In-Memory Fixes It)
February 01, 2026
3 min

Quick Links

Contact Us

Social Media