Blog

All Blog Posts  |  Next Post  |  Previous Post

TMS BIZ Got an Upgrade — See What's New

Monday, February 23, 2026

The February 2026 release of TMS BIZ brings improvements across multiple libraries. This post walks through the most significant changes — what they solve, how to use them, and why they matter for your Delphi applications.

Refresh Tokens in TMS Sphinx: Keep Users Logged In Securely

When you build an application secured with OAuth 2.0, access tokens are intentionally short-lived. That limits exposure if a token is intercepted — but it also means your users get logged out when the token expires. For desktop apps, mobile apps, or any application with long-running sessions, that friction is unacceptable.

TMS Sphinx — an OAuth 2.0 and OpenID Connect authentication server framework for Delphi developers — now implements refresh tokens. A refresh token lets the client application silently obtain a new access token when the old one expires, without asking the user to log in again.

Enabling it on the server requires adding the gtRefreshToken grant type and the offline_access scope to your client application:

Client := SphinxConfig1.Clients.Add;
Client.ClientId := 'myclient';
Client.AllowedGrantTypes := [TGrantType.gtAuthorizationCode, TGrantType.gtRefreshToken];
Client.ValidScopes.Add('openid');
Client.ValidScopes.Add('offline_access');  // Required to get a refresh token
Client.RefreshTokenLifetime := 30 * 24 * 60 * 60;  // 30 days, in seconds

Sphinx also implements refresh token rotation: each time a refresh token is used, it is invalidated and a new one is issued. If a consumed token is reused, Sphinx rejects it — this signals a possible theft. The full flow, client-side code, and security details are covered in depth in the dedicated blog post: No More Unexpected Logouts: Refresh Tokens in TMS Sphinx.

Refresh token documentation →


Single Sign-On Across Applications

If you have multiple applications sharing the same Sphinx authorization server, users previously had to log in to each one individually. That is no longer the case.

Sphinx 2.0 improves Single Sign-On (SSO): when a user has already authenticated with Sphinx and a valid session cookie is present, authorization requests from other client applications skip the login UI entirely. An authorization code is issued silently, and the user never sees a login page.

This means that if a user is already logged into your CRM web application, opening your reporting tool (also protected by the same Sphinx server) will authenticate them transparently — no username, no password, no waiting.

Controlling SSO Behavior with the prompt Parameter

The OpenID Connect specification defines a prompt query parameter for the authorization endpoint that lets you control SSO behavior per request. Sphinx now supports it:

ValueBehavior
(omitted)Use SSO if a session exists; show login UI if not
noneRequire SSO; return login_required error if no session
loginForce re-authentication even if a session exists

This is useful in scenarios where you need to verify the user's identity for a sensitive operation — for example, before displaying or modifying billing data — even though the user is already "logged in" from the application's perspective:

GET /oauth/authorize?
  client_id=myclient&
  response_type=code&
  scope=openid&
  redirect_uri=https://myapp/callback&
  prompt=login

The prompt=none value is equally useful for background token renewal in single-page applications: you attempt a silent authorization, and if it fails with login_required, you redirect the user to the login page.


Sphinx Login App Now Speaks 22 Languages

If your application serves users across multiple countries, or if your user base simply does not speak English, the login experience should feel native to them — not something obviously bolted on from a different locale. Sphinx includes a ready-made login web application that handles sign-in, registration, password reset, and two-factor authentication. That application is now fully localized in 22 languages.

The complete list: English, German, Spanish, French (France and Canada), Brazilian Portuguese, Portuguese, Italian, Chinese (Simplified), Japanese, Korean, Hindi, Russian, Polish, Dutch (Netherlands and Belgium), Swedish, Norwegian, Finnish, Hungarian, Danish, and Czech.

Login page in German

Password page in Japanese

This rounds out years of incremental localization work into a comprehensive offering. If your target market isn't covered yet, Sphinx's login app is designed to be extensible — language files are plain JSON and can be customized or supplemented. But you can simply ask us directly to add new languages, and we'll prioritize them based on demand.


UX Hardening

No more "transaction expired" errors

Previously, if the login transaction expired while a user was filling in the form (they paused for too long, or a browser tab was left idle), they would see a confusing error about an expired transaction, and they could not continue the login process.

Now the login app automatically attempts to silently refresh the transaction several times before giving up. If the transaction still cannot be renewed, the user sees a clear "session expired" message with a button to start over — no cryptic errors, no dead end.

The new TSphinxClientApp.ClientAppUrl property lets you configure where users are redirected after such a timeout, making it easy to return them to the right place in your application rather than to a generic error page.

Accessibility: ARIA Attributes

The login web app now implements ARIA attributes throughout its forms and interactive elements. This makes the login experience significantly better for users relying on screen readers, meeting accessibility standards that are increasingly required for enterprise software.

Bootstrap 5 Update

The CSS framework used by the login app has been updated to the latest version of Bootstrap 5, improving responsiveness and mobile device compatibility. Note: if you have customized the login app's CSS, review your customizations for compatibility with Bootstrap 5's changes.


Sphinx Security

The same Sphinx 2.0 release includes a broad set of security improvements and UX refinements to the built-in login web application. Individually each is a targeted fix; together they represent a meaningful step forward in production-readiness.

Works Without Internet Access

The Sphinx login web app previously loaded UI dependencies (CSS, fonts, JavaScript) from a CDN. This meant that in air-gapped environments — corporate intranets, private clouds, or deployments behind strict firewalls — the login page could fail to render correctly.

All CDN dependencies have been removed. The login app now loads everything from the Sphinx server itself. No external network access is required.

Improved Content Security Policy

Along with removing CDN dependencies, the login app now sets stricter Content Security Policy (CSP) headers and adds X-Content-Type-Options. These headers restrict what the browser is allowed to execute, significantly reducing the impact of a potential injection attack.

SameSite Cookie Protection Against CSRF

The Sphinx session cookie now includes the SameSite=Lax attribute. This attribute tells browsers not to send the session cookie along with cross-site requests initiated from third-party pages — one of the standard mitigations against Cross-Site Request Forgery (CSRF) attacks.

For a deep dive into the full security story behind Sphinx's login application — CDN-free assets, CSP headers, SameSite cookies, 2FA, and more — see the blog post Add User Authentication to Your App in a Secure Way.


Aurelius: Lazy-Loaded Associations in Dynamic Properties

TMS Aurelius is an ORM framework for Delphi that maps your classes to database tables. One of its more advanced features is dynamic properties: the ability to add ORM-mapped properties to a class at runtime, without modifying its source code.

This is useful in multi-tenant applications where the database schema varies per customer, or when integrating with external schemas you don't control. Rather than defining a fixed class with fixed columns, you register additional properties at startup based on configuration.

Until now, dynamic properties supported scalar values (strings, integers, enumerations, blobs) and associations, but not lazy-loaded (proxied) associations or many-valued associations. This limitation meant you couldn't defer the loading of related entities through a dynamic property — all associated objects were loaded eagerly.

That restriction is now lifted.

Registering Lazy and Many-Valued Dynamic Associations

Here is how you can register both a proxied (lazy-loaded) association and a many-valued association as dynamic properties:

uses
  Aurelius.Mapping.Setup,
  Aurelius.Mapping.Attributes;

procedure TDataModule1.CreateDynamicProps(ASetup: TMappingSetup);
var
  PersonProps: TList<TDynamicProperty>;
  Prop: TDynamicProperty;
begin
  PersonProps := ASetup.DynamicProps[TPerson];

  // Lazy-loaded (proxied) association — only loaded from DB when accessed
  Prop := TDynamicProperty.Create('Props', 'LazyCustomer', TypeInfo(Proxy<TCustomer>));
  PersonProps.Add(Prop);
  Prop.AddAssociation(Association.Create([TAssociationProp.Lazy], CascadeTypeAllButRemove));
  Prop.AddColumn(JoinColumn.Create('LAZYCUSTOMER_ID', []));

  // Many-valued association — collection of related entities
  Prop := TDynamicProperty.Create('Props', 'Items', TypeInfo(TList<TInvoiceItem>));
  PersonProps.Add(Prop);
  Prop.AddAssociation(ManyValuedAssociation.Create([], CascadeTypeAllRemoveOrphan));
  Prop.AddColumn(ForeignJoinColumn.Create('PERSON_ID', [TColumnProp.Required]));
end;

When you access Person.Props['LazyCustomer'], Aurelius will fetch the related TCustomer from the database on demand, just as it would for a regular Proxy<TCustomer> property declared directly in the class. The Items collection behaves the same as a regular many-valued association: Aurelius manages its loading, cascading, and orphan deletion.

This makes dynamic properties a viable replacement for static class declarations even in cases involving complex object graphs.

Dynamic properties documentation →


Sparkle: Automatic Locale Detection from HTTP Headers

TMS Sparkle is a high-performance HTTP server framework for Delphi. It serves as the foundation for TMS XData and TMS Sphinx, but you can also use it directly for building HTTP servers, proxies, and middleware pipelines.

Building a multilingual API means every request handler needs to know which language the client prefers. The standard mechanism is the HTTP Accept-Language header, which browsers and HTTP clients send automatically. Parsing it correctly — handling quality factors (q=), fallback chains, and partial matches — is tedious to get right.

The new TLocaleMiddleware (declared in unit Sparkle.Middleware.Locale) handles this automatically. Add it to your server module just like any other Sparkle middleware. After it runs, any downstream middleware or service operation can retrieve the negotiated locale from the request context via Context.Item<ILocale>.

Adding the middleware

uses
  {...}, Sparkle.Middleware.Locale;

// Add to your server module during setup
Module.AddMiddleware(TLocaleMiddleware.Create);

Reading the resolved locale

The ILocale interface exposes Name (full BCP 47 tag, e.g. pt-BR), Language (e.g. pt), and Region (e.g. BR). In an XData service operation, access it through the current context:

uses
  {...}, Sparkle.HttpServer.Context, Sparkle.Middleware.Locale;

function TOrderService.GetGreeting: string;
var
  Locale: ILocale;
begin
  Locale := THttpServerContext.Current.Item<ILocale>;
  if Locale <> nil then
    Result := GetLocalizedGreeting(Locale.Name)
  else
    Result := GetLocalizedGreeting('en'); // fallback
end;

Restricting to supported locales

By default the middleware accepts any locale the client sends. To limit it to the languages your application actually supports, assign the OnAcceptLocale callback:

var
  Middleware: TLocaleMiddleware;
const
  SupportedLocales: array of string = ['en', 'de', 'pt-BR', 'ja'];
begin
  Middleware := TLocaleMiddleware.Create;
  Middleware.DefaultLocale := 'en';
  Middleware.OnAcceptLocale :=
    procedure(var Locale: ILocale; var Accept: Boolean)
    var
      Tag: string;
    begin
      Accept := False;
      for Tag in SupportedLocales do
        if SameText(Tag, Locale.Name) or SameText(Tag, Locale.Language) then
        begin
          Accept := True;
          Break;
        end;
    end;
  Module.AddMiddleware(Middleware);
end;

When no accepted locale is found, the middleware falls back to DefaultLocale. It also sets the Content-Language response header and a Vary header automatically, which is important for HTTP caches that serve different language variants from the same URL.


XData: Retrieve Updated Entities After PUT and POST

TMS XData is a REST API framework for Delphi built on top of TMS Sparkle and TMS Aurelius. The TXDataClient class provides a high-level Delphi API for interacting with XData servers — you work with strongly-typed objects and let the client handle HTTP and JSON.

A common pattern when working with REST APIs is the "POST-then-GET" or "PUT-then-GET" cycle: you create or update an entity, and then fetch it again to get the server-assigned values (auto-generated IDs, timestamps, computed fields, version numbers). This required two round-trips.

The new PostFetch and PutFetch methods combine both operations into one: they send the create or update request and return the entity as it exists on the server after the operation.

Before and After

Before — two round-trips required:

var
  Order: TOrder;
  UpdatedOrder: TOrder;
begin
  Order := TOrder.Create;
  Order.CustomerName := 'Acme Corp';
  Order.CreatedAt := Now;

  Client.Post(Order);  // Server assigns OrderId and Version

  // Must fetch again to get server-assigned values
  UpdatedOrder := Client.Get<TOrder>(Order.OrderId);
  // Now UpdatedOrder has the latest data from server
end;

After — one round-trip with PostFetch:

var
  Order: TOrder;
begin
  Order := TOrder.Create;
  Order.CustomerName := 'Acme Corp';
  Order.CreatedAt := Now;

  // PostFetch sends the POST and returns the entity as the server sees it
  Order := Client.PostFetch<TOrder>(Order);
  // Order.OrderId and Order.Version are now populated from the server response
end;

The same works for updates with PutFetch:

var
  UpdatedOrder: TOrder;
begin
  Order.Status := 'Shipped';
  Order.ShippedAt := Now;

  // PutFetch sends the PUT and returns the refreshed entity
  UpdatedOrder := Client.PutFetch<TOrder>(Order);
  // UpdatedOrder.Version is now the new version from the server
end;

This is particularly useful when using Aurelius's optimistic concurrency control ([Version] attribute), where the server increments a version counter on every update. Previously, keeping the client-side entity in sync with the server's version required an explicit GET. With PutFetch, the updated version is returned automatically.


BCL: Complete Documentation Now Available

TMS BIZ Core Library (BCL) is the foundational library used by all other TMS BIZ products. It provides JSON processing, generic collections, logging infrastructure, internationalization support, and code generation utilities.

With version 2.0, BCL gains its first comprehensive documentation. This includes:

  • A full guide covering the JSON framework, with serialization, deserialization, and the JSON document model
  • Documentation for the core types — collections, observables, nullable types, and base classes
  • Coverage of the logging framework and how to configure log targets
  • Documentation for the internationalization framework used throughout TMS BIZ
  • A complete API reference for all public types and members

If you rely on BCL types in your own code — or want to understand how the TMS BIZ libraries work under the hood — this documentation is now available at the links below.

BCL Documentation →


Summary

ProductVersionHighlights
TMS Sphinx2.0Refresh tokens, SSO, security hardening, 22-language login app
TMS Aurelius5.24Lazy and many-valued associations in dynamic properties
TMS Sparkle3.34TLocaleMiddleware for automatic locale detection
TMS XData5.23PutFetch / PostFetch — retrieve updated entity in one round-trip
TMS BCL2.0First complete documentation release

All updates are available as a one-click update through TMS Smart Setup.



Wagner Landgraf




This blog post has not received any comments yet. Add a comment.



All Blog Posts  |  Next Post  |  Previous Post