Auth

TsgcHTMLAuth: server-side sessions, login state and CSRF protection for an sgcHTML application, in Delphi, C++ Builder and .NET.

TsgcHTMLAuth

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.

Component class

TsgcHTMLAuth with TsgcHTMLSessionStore_Memory, TsgcHTMLSessionStore_File and TsgcHTMLSession (units sgcHTML_Auth and sgcHTML_Session)

Renders

No markup: session cookies, CSRF tokens and login redirects

Languages

Delphi, C++ Builder, .NET

Create a store, create the Auth, protect a route

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

Key properties & methods

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.

Timeouts

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.

CSRF tokens

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.

CSRF with htmx

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.

Remember me

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.

Login rate limit

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.

Passwords

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.

Session stores

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.

The session

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.

Sign in and out

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.

Route gates

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.

WebSocket connections

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.

Keep exploring

Online HelpFull API reference and usage guide for this component.
Sessions and authentication guideCookies, CSRF, route gates, remember-me and the login rate limit, step by step.
All sgcHTML ComponentsBrowse the full feature matrix of 80+ components.
Download Free TrialThe 30-day trial ships the 60.HTML demo projects, including 02.AdminCRUD, which signs in through TsgcHTMLAuth.
PricingSingle, Team and Site licenses with full source code.
Best value: All-AccessEvery eSeGeCe product, Premium Support included, from €1,059/year.
See All-Access pricing

Ready to Get Started?

Download the free trial and start building web UIs in Delphi, C++ Builder and .NET.