Sphinx multitenant – how to pass tenantId when using TSphinxLogin on the client?

Hi,

I’m using TMS Sphinx in a user-level multitenant scenario. In my authentication server I allow the same user to exist in different tenants, so the request must include the tenant_id for the filter to work.

On the server side, in the OnManagerCreate event, I do this:

procedure TdtmAPIAutenticacaoServidor.SphinxServerAPIManagerCreate(
  Sender: TObject; Args: TManagerCreateArgs);
begin
  var TenantId: String := Args.Handler.Context.RequiredItem<ITenant>.TenantId;

  Args.Manager.EnableFilter('Multitenant').SetParam('tenant_id', TenantId);
end;

With that, Sphinx applies the filter correctly as long as the request actually contains the tenant.

The issue is on the client side: I’m using the TSphinxLogin component (the one that builds the authorization URL for me), and I couldn’t find a place to tell it which tenant I want to authenticate against. So when the URL is generated, this information is not added, and my auth server responds that the tenantId is required.

If I manually tweak the URL and add the query param, for example:

.../connect/authorize?...&X-Tenant-Id=123

then it works. So the backend is fine and the multitenant filter is working.

My question is:
is there currently an official way in TSphinxLogin to:

  • add extra query parameters to the authorization URL (such as X-Tenant-Id or tenant_id);
  • or intercept the URL right before it is used so I can append the tenant;
  • or some property that was meant for multitenant scenarios?

From what I saw, the component doesn’t expose something like ExtraParams for this. Before I subclass the component or build the flow manually, I wanted to check if there is already a recommended extension point from TMS.

Context recap:

  • Sphinx server with multitenant filter working;
  • Delphi client using TSphinxLogin;
  • server requires tenantId;
  • if I add &X-Tenant-Id=123 manually, it works;
  • I need to do that directly through the component.

If there is an event/property I missed, or if this should be considered as a small enhancement request for the component (to allow custom query params), I’d really appreciate some guidance.

Multitenancy can be achieved in many ways. The Tenant Middleware in Sparkle provides several ways to "detect" a tenant from a request:

  • From a different URL path (http://server/base/tenant1 and http://server/base/tenant2)
  • From URL query param (http://server/base?tenant=1)
  • From a HTTP header (X-Tenant-Id=1)
  • From subdomains (tenant1.myserver.com, tenant2.myserver.com)
  • From JWT user claims (tenant_id=1 inside a JWT payload)
  • From mapped domains (sometenant.com maps to tenant1, anotherone.com maps to tenant2, etc.

Using query path is one of the worse choices for authentication server because all the code authorization, token issuing, etc., pass through receiving and sending specific standard query parameters. It's really standard and error prone to modify those parameters and make sure they keep custom ones.

HTTP header is also not very good because you can't simply modify the requests made a web browser, and OAuth involves HTTP redirects where there is no full control of headers from JavaScript.

So I'd recommend using any of the approaches. Domain/subdomain is the best for a robust, production server where you want to offer white-label options, for example.

For simpler setups where you don't want to handle domains and subdomains, I'd recommend multi tenancy using base path. So choosing the tenant from the client is simply choosing the respective URL.

1 Like

Hello!

I used the BasePath approach, and it worked well for me — now my Sphinx server correctly receives the TenantId.

However, when I add the following code in my Sphinx server:

procedure TdtmAPIAutenticacaoServidor.SphinxServerAPIManagerCreate(
  Sender: TObject; Args: TManagerCreateArgs);
begin
  var TenantId: String := Args.Handler.Context.RequiredItem<ITenant>.TenantId;
  Args.Manager.EnableFilter('Multitenant').SetParam('tenant_id', TenantId);
end;

I start getting the error:

Invalid transaction id

If I remove the filter configuration, everything works normally — but then the login process no longer respects tenant isolation, and all users become visible to all tenants.

What I’m trying to achieve is:

  • Keep Sphinx fully functional with BasePath-based multitenancy.
  • Automatically apply the tenant_id filter to users, so each tenant can only log in with its own users.
  • When creating a new user, automatically store the tenant_id in the sx_users table.

So the BasePath detection is working correctly, but enabling the Aurelius filter inside SphinxServerAPIManagerCreate seems to interfere with Sphinx’s internal queries (probably the login transaction table).

I’d like to know the correct approach to make this multitenant setup work properly with Sphinx — using BasePath while still being able to filter and create users per tenant without breaking the login flow.

Probably you are missing the filter enforcer. I mean, you enabled the filter so every query is filtered by tenant_id, but you didn't make sure that all record inserts and updates set the tenant_id field value:

uses {...}, Sphinx.Consts;

...

initialization
  Enforcer := TFilterEnforcer.Create('Multitenant', 'tenant_id', 'FTenantId');
  Enforcer.AutoComplyOnInsert := True;
  Enforcer.AutoComplyOnUpdate := True;
  Enforcer.Activate(TMappingExplorer.Get(cSphinxModelName));

finalization
  Enforcer.Deactivate(TMappingExplorer.Get(cSphinxModelName));
  Enforcer.Free;