I found a method for storing user data in a server-side session. I did this by using the CircuitHandler Id as a ‘token’ for the user to access the system. Only the Username and CircuitId gets stored in the client LocalStorage (using Blazored.LocalStorage); other user data is stored in the server. I know it's a lot of code, but this was the best way I could find to keep user data secure on the server-side.
UserModel.cs (for client-side LocalStorage)
public class UserModel
{
public string Username { get; set; }
public string CircuitId { get; set; }
}
SessionModel.cs (the model for my Server-side session)
public class SessionModel
{
public string Username { get; set; }
public string CircuitId { get; set; }
public DateTime DateTimeAdded { get; set; } //this could be used to timeout the session
//My user data to be stored server side...
public int UserRole { get; set; }
etc...
}
SessionData.cs (keeps a list of all active sessions on the server)
public class SessionData
{
private List<SessionModel> sessions = new List<SessionModel>();
private readonly ILogger _logger;
public List<SessionModel> Sessions { get { return sessions; } }
public SessionData(ILogger<SessionData> logger)
{
_logger = logger;
}
public void Add(SessionModel model)
{
model.DateTimeAdded = DateTime.Now;
sessions.Add(model);
_logger.LogInformation("Session created. User:{0}, CircuitId:{1}", model.Username, model.CircuitId);
}
//Delete the session by username
public void Delete(string token)
{
//Determine if the token matches a current session in progress
var matchingSession = sessions.FirstOrDefault(s => s.Token == token);
if (matchingSession != null)
{
_logger.LogInformation("Session deleted. User:{0}, Token:{1}", matchingSession.Username, matchingSession.CircuitId);
//remove the session
sessions.RemoveAll(s => s.Token == token);
}
}
public SessionModel Get(string circuitId)
{
return sessions.FirstOrDefault(s => s.CircuitId == circuitId);
}
}
CircuitHandlerService.cs
public class CircuitHandlerService : CircuitHandler
{
public string CircuitId { get; set; }
public SessionData sessionData { get; set; }
public CircuitHandlerService(SessionData sessionData)
{
this.sessionData = sessionData;
}
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
CircuitId = circuit.Id;
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
//when the circuit is closing, attempt to delete the session
// this will happen if the current circuit represents the main window
sessionData.Delete(circuit.Id);
return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
return base.OnConnectionDownAsync(circuit, cancellationToken);
}
public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
return base.OnConnectionUpAsync(circuit, cancellationToken);
}
}
Login.razor
@inject ILocalStorageService localStorage
@inject SessionData sessionData
....
public SessionModel session { get; set; } = new SessionModel();
...
if (isUserAuthenticated == true)
{
//assign the sesssion token based on the current CircuitId
session.CircuitId = (circuitHandler as CircuitHandlerService).CircuitId;
sessionData.Add(session);
//Then, store the username in the browser storage
// this username will be used to access the session as needed
UserModel user = new UserModel
{
Username = session.Username,
CircuitId = session.CircuitId
};
await localStorage.SetItemAsync("userSession", user);
NavigationManager.NavigateTo("Home");
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddServerSideBlazor();
services.AddScoped<CircuitHandler>((sp) => new CircuitHandlerService(sp.GetRequiredService<SessionData>()));
services.AddSingleton<SessionData>();
services.AddBlazoredLocalStorage();
...
}