Skip to content

UserSession ​

One refresh-token session. Created at login and registration, rotated on refresh, and revoked by logout, a password change, or reuse detection.

DANGER

UserSession is a database entity. Never return it from an endpoint: it carries the current and previous refresh-token hashes and the surrogate keys. Map it to a DTO that exposes only the fields the client needs, such as Device, Browser, Os, and LastActiveAt for a session list.

Usage ​

csharp
using Microsoft.EntityFrameworkCore;
using AlmightyShogun.AspNet.Auth.Credentials;

public sealed class SessionAuditService(AppDbContext database)
{
    public Task<List<UserSession>> GetActiveSessionsAsync(int userId)
        => database.UserSessions
            .Where(session => !session.IsRevoked)
            .Where(session => session.UserId == userId)
            .Where(session => session.ExpiresAt > DateTimeOffset.UtcNow)
            .ToListAsync();
}

Fields

Id: int
The surrogate key. Never leaves the server; the refresh token is the only handle a client has on a session.

UserId: int
The owning user. Sessions cascade with the user, so deleting an account takes its sessions with it.

RefreshTokenHash: string
Hash of the current refresh token, uniquely indexed. The token itself is never stored, so a database copy cannot be used to refresh.

PreviousRefreshTokenHash: string?
Hash of the token this one replaced. Presenting it again after the grace period is what identifies a stolen token and revokes every session for the user. Cleared as that fires, so one retired hash triggers detection once.
Default: null

App: string?
The application audience this session belongs to. Refresh only matches sessions for the currently resolved application, so a token from one app cannot refresh another.
Default: null

ExpiresAt: DateTimeOffset
When the session stops refreshing. Set a refresh window ahead of the moment the session opens and set the same way again on each refresh rather than accumulating, and never past the ceiling set by AbsoluteSessionLifetimeDays.

CreatedAt: DateTimeOffset
When the session began. The absolute lifetime cap is measured from here, not from the last refresh.

LastActiveAt: DateTimeOffset
When the session last refreshed. Also what the reuse grace period is measured against.

IsRevoked: bool
Whether the session was ended deliberately, by logout, a password change, or reuse detection. Revoked rows are kept rather than deleted.
Default: false

IpAddress: string?
The address of the most recent request on this session, truncated to 45 characters, which holds any IPv6 address in its longest plain form but not one carrying a scope id.
Default: null

UserAgent: string?
The raw User-Agent header, kept alongside the parsed values because parsing loses detail that matters when auditing.
Default: null

Device: string?
The device parsed from the User-Agent, for showing a user their own session list.
Default: null

Browser: string?
The browser parsed from the User-Agent, for showing a user their own session list.
Default: null

Os: string?
The operating system parsed from the User-Agent, for showing a user their own session list.
Default: null

IsExpired: bool
Whether the expiry has passed. Computed, not mapped, so it cannot be used in a query.

IsActive: bool
Whether the session would still refresh, meaning neither revoked nor expired. Computed, not mapped.

All packages are released under the MIT License.