Blog

All Blog Posts  |  Next Post  |  Previous Post

Login with Google, Microsoft and More: Identity Brokering in TMS Sphinx

Today

Almost every modern application offers it: a row of buttons that say "Continue with Google", "Sign in with Microsoft", or "Login with Apple". Users have come to expect it — they don't want yet another password to remember, and you don't want the liability of storing one. At the same time, corporate customers increasingly demand that your app authenticate against their identity provider, so employees use the same single sign-on they use for everything else.

Until now, wiring this up in a Delphi backend meant a lot of plumbing: redirect dances, PKCE, state and nonce handling, token validation, and the headache of mapping an external account to a user in your own database — repeated for every provider you wanted to support.

The next version of TMS Sphinx removes that plumbing. Sphinx can now act as an identity broker, delegating authentication to upstream providers like Google or Microsoft Entra while still issuing and controlling its own tokens, users and sessions.

Login page with external providers


A quick refresher on TMS Sphinx

TMS Sphinx is an OAuth 2.0 and OpenID Connect authentication and authorization server framework for Delphi. It lets you add secure, standards-based login to your own applications — desktop, web, mobile and APIs — with Sphinx acting as the identity provider that issues and validates tokens.

Your client applications already talk to Sphinx as their OpenID Connect provider. With identity brokering, that doesn't change at all. The new part happens entirely on the server side.


What is identity brokering?

An identity broker (also called an IdP proxy or identity federation) sits between your applications and one or more external identity providers:

  • Your client apps keep talking only to Sphinx, exactly as before.
  • Behind the scenes, Sphinx delegates the actual authentication to an upstream provider — Google, Microsoft Entra, a corporate IdP, even another Sphinx server.
  • Sphinx receives the upstream result, maps that external identity to a local user, and completes the original request with its own Sphinx-issued tokens.

The key benefit: the tokens your applications receive are always issued and signed by Sphinx. The upstream tokens are used only to establish who the user is, and are then discarded (unless you explicitly choose to keep them). Your scopes, your claims, your sessions, your user database — all stay under your control.

This is exactly what you want when you need to:

  • Offer familiar "Login with Google / Microsoft / GitHub / Apple / Facebook" buttons.
  • Authenticate users against an existing corporate identity provider for a B2B customer.
  • Support multi-tenant scenarios where different customers use different identity providers.
  • Keep issuing your own tokens and managing your own users regardless of how people sign in.


Registering an upstream provider

Providers are registered in the new ExternalProviders collection of TSphinxConfig. You can add them at design time (double-click the ExternalProviders property) or from code. At a minimum you give the provider a unique name, the authority URL, and the client id/secret you registered for Sphinx at that provider:

  Provider := SphinxConfig1.ExternalProviders.Add;
  Provider.Name := 'google';
  Provider.DisplayName := 'Google';
  Provider.Authority := 'https://accounts.google.com';
  Provider.ClientId := 'your-client-id';
  Provider.ClientSecret := 'your-client-secret';
  Provider.Scope := 'openid email profile';
  Provider.AutoDiscover := True;

When AutoDiscover is True (the default), Sphinx fetches the provider's discovery document from Authority + "/.well-known/openid-configuration" to learn its endpoints automatically. The redirect URI you register at the upstream provider is always Sphinx's own callback endpoint, oauth/external/callback.

That's it. No manual handling of authorization codes, PKCE verifiers, state, nonce or token validation — Sphinx does all of it for you.


Adding "Login with..." buttons

To let the end user pick a provider on the Sphinx login page, just set ShowInLoginPage to True. Use DisplayName for the button text and IconUrl for the icon:

  Provider := SphinxConfig1.ExternalProviders.Add;
  Provider.Name := 'google';
  Provider.DisplayName := 'Google';
  Provider.IconUrl := 'img/providers/google.svg'; // a built-in icon
  Provider.ShowInLoginPage := True;
  // ... authority, client id/secret, scope as above

A nice touch: Sphinx ships ready-to-use, brand-colored icons for the most common providers, so you don't have to source and host them yourself. Just point IconUrl at one of the built-in relative paths:

ProviderIconUrl value
Googleimg/providers/google.svg
Microsoftimg/providers/microsoft.svg
GitHubimg/providers/github.svg
Appleimg/providers/apple.svg
Facebookimg/providers/facebook.svg

The user clicks the button, authenticates at the provider's own consent screen, and comes right back — logged into your app.

Google consent screen

Want a login page with no password form at all? Set LoginOptions.AllowPasswordLogin := False and the page shows only the provider buttons (with a safeguard that still shows the local form if no providers happen to be available, so you can't lock everyone out).


Delegating transparently (no buttons)

Sometimes you don't want the user to choose — you want a specific client, tenant or scope to always authenticate against a particular provider. The OnResolveExternalProvider event is the single decision point that turns brokering on for a request:

procedure TForm1.SphinxConfig1ResolveExternalProvider(Sender: TObject;
  Args: TResolveExternalProviderArgs);
begin
  // Transparently delegate every request to the corporate identity provider.
  Args.ProviderName := 'corporate-idp';
end;

Because the logic is entirely yours, you can delegate only for certain clients or tenants and fall back to the normal Sphinx login for everyone else. The user never sees a Sphinx login screen at all — they go straight to the corporate IdP and back.


Mapping the external user to a local user — without code

After the upstream login completes, Sphinx needs to know which local user that identity corresponds to. The recommended, durable key is the pair (provider, subject) — the provider name plus the stable sub claim, which (unlike an e-mail address) never changes. Sphinx persists this in a new link table via the TUserLogin entity.

For the common cases you don't have to write a single line of code. Just configure the new built-in reconciliation policy:

  // Link an external sign-in to an existing account by verified e-mail...
  SphinxConfig1.ExternalLoginOptions.AllowAutoLinkByEmail := True;
  // ...and create a brand-new local account on first sign-in.
  SphinxConfig1.ExternalLoginOptions.AllowAutoProvision := True;

With this, Sphinx automatically:

  1. Resolves an existing link by (provider, subject).
  2. If none, links to an existing user matching by verified e-mail.
  3. If still none, provisions a new local user from the upstream profile.

All options default to off, so nothing happens automatically until you opt in. When a new user is created, the OnExternalUserProvisioned event fires so you can finish the account (assign roles, copy profile fields, etc.).

And if a provider doesn't return everything you require — Facebook, famously, returns no e-mail — Sphinx automatically shows a "complete your profile" step asking the user for the missing field, then continues. Again, no code on your part.


Full control when you need it

For everything else, the OnExternalSignIn event gives you complete control over the mapping. It receives convenient access to the subject, e-mail and verification status, plus a user manager bound to the current request:

procedure TForm1.SphinxConfig1ExternalSignIn(Sender: TObject;
  Args: TExternalSignInArgs);
var
  User: TUser;
begin
  // Resolve first by the durable link (provider + subject).
  User := Args.UserManager.FindByLogin(Args.ProviderName, Args.Subject);

  // First sign-in: match an existing account by e-mail and remember the link.
  if User = nil then
  begin
    if not Args.EmailVerified then
    begin
      Args.Reject('A verified email is required');
      Exit;
    end;
    User := Args.UserManager.FindByEmail(Args.Email);
    if User <> nil then
      Args.UserManager.AddLogin(User, Args.ProviderName, Args.Subject);
  end;

  Args.User := User;
end;


Built for the real world

A few details that show this is more than a demo feature:

  • Multi-tenant Microsoft Entra works out of the box. When a provider's issuer is a {tenantid} template (as Entra returns for the organizations/common authorities), Sphinx resolves the placeholder with each token's tenant id before validating — so issuer validation stays enabled and secure, no workarounds needed.
  • Two-factor authentication is respected. An external sign-in still honors the user's Sphinx 2FA configuration. Or, when the upstream already performed MFA, you can trust it and skip the second challenge with Args.SkipTwoFactor := True.
  • You can keep the upstream tokens. Enable ExternalLoginOptions.SaveTokens and Sphinx persists the upstream access_token, refresh_token and id_token so your app can later call the provider's APIs on the user's behalf via GetAuthenticationToken.
  • Security is built in. PKCE, state and nonce are generated and validated by Sphinx, the callback is bound to the originating browser with a single-use cookie, and client secrets never leave the server.


Try it in the Simple demo

The Simple demo server (demos/Simple/Server) ships a ready-to-fill template in ConfigureExternalProviders (MainForm.pas). Register the demo's callback URL, http://localhost:2001/tms/sphinx/oauth/external/callback, as an authorized redirect URI in the Google Cloud Console, drop in your own client id and secret, and a "Continue with Google" button appears on the login page. The demo's OnExternalSignIn handler resolves the user by link, falls back to e-mail, and provisions a local user on first sign-in — so the whole brokered flow works end to end.


Conclusion

Identity brokering brings one of the most-requested features in modern authentication to Delphi developers, with very little effort:

  • Better user experience — familiar "Login with..." buttons and no extra passwords.
  • Enterprise-ready — authenticate against corporate identity providers and multi-tenant Entra.
  • You stay in control — Sphinx always issues its own tokens and owns your users, scopes and sessions.
  • Less code — automatic reconciliation, provisioning and profile completion mean common scenarios need almost no code at all.

To dive deeper, read the full Identity brokering documentation and the release notes, and learn more about TMS Sphinx on its product page.



Wagner Landgraf




This blog post has not received any comments yet.



Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post