Auth
TsgcHTMLAuth: server-side sessions, login state and CSRF protection for an sgcHTML application, in Delphi, C++ Builder and .NET.
TsgcHTMLAuth: server-side sessions, login state and CSRF protection for an sgcHTML application, in Delphi, C++ Builder and .NET.
A non-visual component that owns the session cookie, the signed-in user and the CSRF token of every request. Assign a session store, handle OnAuthenticate to check the password, set the Auth property of the HTMX engine and mark routes with RequireLogin. It is part of sgcHTML, which is sold independently as a standalone pack.
TsgcHTMLAuth with TsgcHTMLSessionStore_Memory, TsgcHTMLSessionStore_File and TsgcHTMLSession (units sgcHTML_Auth and sgcHTML_Session)
No markup: session cookies, CSRF tokens and login redirects
Delphi, C++ Builder, .NET
Assign a session store, handle OnAuthenticate, set Auth on the engine and mark the routes to protect. From then on the engine reads the session of every request, checks the CSRF token and redirects anonymous users to LoginPath.
uses
sgcHTML_Session, sgcHTML_Auth, sgcHTMX_Router, sgcHTMX_Engine_Server;
// oHTMX is a TsgcHTMX_Engine_Server, oRouter is a TsgcHTMX_Router
var
oStore: TsgcHTMLSessionStore_Memory;
oAuth: TsgcHTMLAuth;
oRoute: TsgcHTMX_Route;
begin
oStore := TsgcHTMLSessionStore_Memory.Create(Self);
oStore.SweepInterval := 60;
oAuth := TsgcHTMLAuth.Create(Self);
oAuth.SessionStore := oStore;
oAuth.IdleTimeout := 1800; // seconds without a request
oAuth.AbsoluteTimeout := 43200; // seconds since sign-in
oAuth.LoginRateLimit.MaxAttempts := 5;
oAuth.OnAuthenticate := AuthAuthenticate;
oHTMX.Auth := oAuth;
// only a signed-in user reaches /orders
oRoute := oRouter.Routes.Add;
oRoute.Path := '/orders';
oRoute.RequireLogin := True;
oRoute.OnRoute := OrdersRoute;
// only a session with one of these roles reaches /admin
oRoute := oRouter.Routes.Add;
oRoute.Path := '/admin';
oRoute.RequireRoles := 'admin,manager';
oRoute.OnRoute := AdminRoute;
end;
procedure TForm1.AuthAuthenticate(Sender: TObject;
const aUser, aPassword: string; var aAccept: Boolean;
var aUserID, aDisplayName: string; const aRoles: TStrings);
begin
// LoadPasswordHash reads the value stored when the user was created
aAccept := sgcHTMLPasswordVerify(aPassword, LoadPasswordHash(aUser));
if aAccept then
begin
aUserID := aUser;
aDisplayName := aUser;
aRoles.Add('admin');
end;
end;
// when you create a user, store this hash, never the password
vHash := sgcHTMLPasswordHash('secret');
// includes: sgcHTML_Session.hpp, sgcHTML_Auth.hpp, sgcHTMX_Router.hpp, sgcHTMX_Engine_Server.hpp
// oHTMX is a TsgcHTMX_Engine_Server, oRouter is a TsgcHTMX_Router
TsgcHTMLSessionStore_Memory *oStore = new TsgcHTMLSessionStore_Memory(this);
oStore->SweepInterval = 60;
TsgcHTMLAuth *oAuth = new TsgcHTMLAuth(this);
oAuth->SessionStore = oStore;
oAuth->IdleTimeout = 1800; // seconds without a request
oAuth->AbsoluteTimeout = 43200; // seconds since sign-in
oAuth->LoginRateLimit->MaxAttempts = 5;
oAuth->OnAuthenticate = AuthAuthenticate;
oHTMX->Auth = oAuth;
// only a signed-in user reaches /orders
TsgcHTMX_Route *oRoute = oRouter->Routes->Add();
oRoute->Path = "/orders";
oRoute->RequireLogin = true;
oRoute->OnRoute = OrdersRoute;
// only a session with one of these roles reaches /admin
oRoute = oRouter->Routes->Add();
oRoute->Path = "/admin";
oRoute->RequireRoles = "admin,manager";
oRoute->OnRoute = AdminRoute;
void __fastcall TForm1::AuthAuthenticate(TObject *Sender,
const UnicodeString aUser, const UnicodeString aPassword, bool &aAccept,
UnicodeString &aUserID, UnicodeString &aDisplayName, TStrings *const aRoles)
{
// LoadPasswordHash reads the value stored when the user was created
aAccept = sgcHTMLPasswordVerify(aPassword, LoadPasswordHash(aUser));
if (aAccept)
{
aUserID = aUser;
aDisplayName = aUser;
aRoles->Add("admin");
}
}
// when you create a user, store this hash, never the password
String hash = sgcHTMLPasswordHash("secret");
using esegece.sgcWebSockets;
// htmx is a TsgcHTMX_Engine_Server, router is a TsgcHTMX_Router
var store = new TsgcHTMLSessionStore_Memory();
store.SweepInterval = 60;
var auth = new TsgcHTMLAuth();
auth.SessionStore = store;
auth.IdleTimeout = 1800; // seconds without a request
auth.AbsoluteTimeout = 43200; // seconds since sign-in
auth.LoginRateLimit.MaxAttempts = 5;
auth.OnAuthenticate += AuthAuthenticate;
htmx.Auth = auth;
// only a signed-in user reaches /orders
var route = router.Routes.Add();
route.Path = "/orders";
route.RequireLogin = true;
route.OnRoute += OrdersRoute;
// only a session with one of these roles reaches /admin
route = router.Routes.Add();
route.Path = "/admin";
route.RequireRoles = "admin,manager";
route.OnRoute += AdminRoute;
void AuthAuthenticate(object sender, string aUser, string aPassword,
ref bool aAccept, ref string aUserID, ref string aDisplayName,
List<string> aRoles)
{
// LoadPasswordHash reads the value stored when the user was created
aAccept = sgcHTMLAuthHelpers.sgcHTMLPasswordVerify(aPassword, LoadPasswordHash(aUser));
if (aAccept)
{
aUserID = aUser;
aDisplayName = aUser;
aRoles.Add("admin");
}
}
// when you create a user, store this hash, never the password
string hash = sgcHTMLAuthHelpers.sgcHTMLPasswordHash("secret");
The members you reach for most often.
CookieName (default sgcsid), CookiePath and CookieDomain shape the cookie. CookieSecure and CookieHttpOnly are True by default and CookieSameSite is hssLax; hssStrict and hssNone are the other values, and hssNone always adds Secure.
IdleTimeout (1800 seconds) ends a session that was not used, AbsoluteTimeout (43200 seconds) ends it that long after sign-in whatever the activity. 0 disables either check. CurrentSession validates both and touches the session on every request.
CSRFProtection is on by default. Every session gets a random token: IssueCSRF(session) returns it and ValidateCSRF(session, token) compares it in constant time. The engine reads the token from the CSRFHeaderName header (X-CSRF-Token) or the CSRFFieldName field (csrf_token) and answers 403 to a state-changing request that lacks it.
A page rendered for a signed-in session carries <meta name="csrf-token"> and <meta name="csrf-header"> in its head, and the bundled htmx script sends the token in that header on every request that is not GET, HEAD or OPTIONS. hx-post and hx-delete need no extra markup.
RememberMeDays (0 means off, at most 3650) turns it on and RememberCookieName names the cookie (sgcrem). When a request has no valid session but a valid token, OnLoadUser reloads the user, a new session is issued and the token is rotated. The store keeps only a hash of the verifier.
LoginRateLimit is a TsgcHTMLAuthRateLimit_Options. Failed logins are counted per client IP and per user name inside WindowSeconds (300); reaching MaxAttempts (5) locks that IP or user for LockoutSeconds (900), and OnAuthenticate is not called while locked. MaxAttempts = 0 disables the limit and IsLoginLocked(ip, user) reports a lock.
sgcHTMLPasswordHash(password, iterations) returns pbkdf2-sha256$iterations$salt$hash with a random 16-byte salt and 210000 iterations by default. sgcHTMLPasswordVerify(password, hash) checks a password in constant time and returns False for a malformed hash.
TsgcHTMLSessionStore_Memory keeps sessions in the process, with SweepInterval for a background sweep; keep its IdleTimeout and AbsoluteTimeout equal to the ones of the Auth. TsgcHTMLSessionStore_File writes one JSON file per session into Folder, so several processes can share it, and rewrites a file at most every TouchInterval seconds (60). Both are thread-safe.
TsgcHTMLSession holds ID, UserID, DisplayName, Roles, free Values (name=value), CSRFToken, CreatedAt, LastSeen, RemoteIP and UserAgentHash, plus HasRole. A route reads it as TsgcHTMXRequest.Session, nil when anonymous. Stores hand out copies, so call SessionStore.Put to keep a change.
Authenticate applies the rate limit and fires OnAuthenticate. SignIn always creates a new session id, deletes the old one and issues a new CSRF token, which defends against session fixation. SignOut ends one session and SignOutEverywhere(userID) ends every session and remember-me token of a user. Outside the engine, call CurrentSession(cookieHeader, remoteIP, userAgent, setCookies) yourself, as the Admin CRUD demo does.
Mark a TsgcHTMX_Route with RequireLogin or RequireRoles (comma separated, one match is enough). An anonymous request is redirected to LoginPath?next= with a 302, or gets 401 plus HX-Redirect when htmx sent it; a session without the role gets 403. The engine also answers POST to LoginPath and LogoutPath, then goes to AfterLoginPath.
A message runs with the session of the cookies sent on the WebSocket handshake, provided that handshake came from the same origin; any other message is anonymous, and a denied message gets no response. TsgcHTMX_Engine_Server.MessageSession(aConnection) returns the session behind a connection for a host that answers OnHTMXMessage itself, and the caller frees it.
| Online HelpFull API reference and usage guide for this component. | Open | |
| Sessions and authentication guideCookies, CSRF, route gates, remember-me and the login rate limit, step by step. | Open | |
| All sgcHTML ComponentsBrowse the full feature matrix of 80+ components. | Open | |
| Download Free TrialThe 30-day trial ships the 60.HTML demo projects, including 02.AdminCRUD, which signs in through TsgcHTMLAuth. | Open | |
| PricingSingle, Team and Site licenses with full source code. | Open |