sgcWebSockets Authentication
Six components covering both halves of authentication: acting as a client that obtains and presents tokens, and acting as a server that issues and validates them. OAuth2 and JWT each have a client and a server, and WebAuthn covers passkeys on the server side.
When to use this skill
- Sign a user in through an OAuth2 provider and get an access token
- Refresh, revoke or introspect a token
- Run your own OAuth2 authorization server
- Issue JWTs, or validate incoming ones on a server
- Add passkey (WebAuthn) registration and login to a server
- Use DPoP sender-constrained tokens
Units
Five of the six live in sgcHTTP:
uses
sgcHTTP; // OAuth2 client, OAuth2 server, OAuth2 server provider,
// JWT client, JWT server
TsgcWSAPIServer_WebAuthn is the exception and lives in
sgcWebSocket_Server_APIs, because it attaches to a WebSocket or HTTP server
rather than standing alone.
These are frequently paired with the REST server in sgcwebsockets-http.
Before you start, ask the developer
Use a structured question tool if your host has one, for example Claude Code's
AskUserQuestion. Otherwise ask in chat:
- Client or server side? Consuming someone else's identity provider is the client. Issuing your own tokens is the server. They are separate components and the answer changes everything that follows.
- Which OAuth2 flow? Authorization code with a local redirect listener is
the usual one for a desktop application, and it needs a local HTTP server on
a loopback port, which is what
LocalServerOptionsconfigures. - Where do the secrets live? Client id and secret must come from
configuration. Never generate source with them inline. Note they sit on
OAuth2Options, not onAuthorizationServerOptions, which holds the provider's endpoint URLs. - Are tokens sender-constrained? DPoP is off unless asked for. Adding it later changes every request, so decide up front.
- Do you already have a user store? Both the OAuth2 server and the JWT
server take a
TsgcHTTPServer_Userscomponent, which is shared with the REST server.
Components in this skill
| Component | Side | Use it for |
|---|---|---|
TsgcHTTP_OAuth2_Client |
client | Obtain, refresh, revoke and introspect tokens |
TsgcHTTP_OAuth2_Server |
server | Run an authorization server |
TsgcHTTP_OAuth2_Server_Provider |
server | Define a provider within that server |
TsgcHTTP_JWT_Client |
client | Build and sign a JWT to send |
TsgcHTTP_JWT_Server |
server | Validate incoming JWTs |
TsgcWSAPIServer_WebAuthn |
server | Passkey registration and authentication |
Quickstart, OAuth2 client
The component runs the flow and holds the result. Configure the endpoints, the local redirect listener and the credentials, then start:
uses
sgcHTTP;
FOAuth := TsgcHTTP_OAuth2_Client.Create(Self);
// credentials and grant type live on OAuth2Options
FOAuth.OAuth2Options.ClientId := GetClientIdFromConfig;
FOAuth.OAuth2Options.ClientSecret := GetClientSecretFromConfig;
// the provider's endpoints live on AuthorizationServerOptions
FOAuth.AuthorizationServerOptions.AuthURL := 'https://provider/authorize';
FOAuth.AuthorizationServerOptions.TokenURL := 'https://provider/token';
// the loopback listener that catches the redirect
FOAuth.LocalServerOptions.Port := 8080;
FOAuth.LocalServerOptions.RedirectURL := 'http://127.0.0.1:8080/';
FOAuth.Start;
// after the flow completes, the token is on the component
vHeader := FOAuth.TokenType + ' ' + FOAuth.AccessToken;
AccessToken, TokenType, CurrentExpiresIn and CurrentRefreshToken are all
read-only. You do not assign them, you read them once the flow has finished.
Token lifecycle is explicit rather than automatic:
FOAuth.Refresh(FOAuth.CurrentRefreshToken);
FOAuth.Introspect(vToken); // ask the server if a token is still valid
FOAuth.Revoke(vToken); // invalidate it
DPoP, when a bearer token is not enough
A plain bearer token is usable by anyone who steals it. DPoP binds the token to
a key your application holds, so a stolen token alone is not enough. Configure
it through DPoPOptions, then attach a proof to each request:
vProof := FOAuth.GetDPoPProof('GET', 'https://api.example.com/me', FOAuth.AccessToken);
The proof is per request, computed from the method and URL, so it cannot be
generated once and reused. DPoPNonce carries the server-supplied nonce when
the authorization server requires one.
Quickstart, JWT
The client builds and signs, the server validates:
// client
FJWT := TsgcHTTP_JWT_Client.Create(Self);
// ... configure FJWT.JWTOptions ...
vToken := FJWT.Sign;
// server
FJWTServer := TsgcHTTP_JWT_Server.Create(Self);
FJWTServer.Users := FUsers;
FJWTServer.OnJWTUnauthorized := JWTUnauthorized;
The server side is event-driven. It exposes OnJWTBeforeRequest,
OnJWTBeforeValidateToken, OnJWTBeforeValidateSignature,
OnJWTAfterValidateToken and OnJWTUnauthorized. Use the before-validate hooks
to supply a key or accept a claim, OnJWTAfterValidateToken to inspect a token
that passed, and OnJWTUnauthorized to shape the rejection.
Quickstart, WebAuthn
WebAuthn is a challenge and response in both directions, and the component gives you the two option payloads the browser needs:
// registration
vOptions := FWebAuthn.GetRegistrationOptionsResponse(vRequestPayload);
// ... browser creates the credential ...
FWebAuthn.ValidateRegistrationOptions(vBrowserPayload);
FWebAuthn.AddCredential(vCredential);
// authentication
vOptions := FWebAuthn.GetAuthenticationOptionsResponse(vRequestPayload);
FWebAuthn.ValidateAuthenticationOptions(vBrowserPayload);
You store the credential records yourself. The component validates, it does not persist.
Things that catch people out
- The OAuth2 token properties are read-only. Trying to assign
AccessTokenwill not compile, and the fix is to run the flow rather than set it by hand. - Credentials and endpoints live on two different option objects.
OAuth2OptionscarriesClientId,ClientSecret,Username,PasswordandGrantType.AuthorizationServerOptionscarriesAuthURL,TokenURL,RevocationURL,IntrospectionURLandDeviceAuthorizationURL. Putting a client id on the second one compiles and then fails at the provider. LocalServerOptions.RedirectURLmust match the redirect URI registered with the provider exactly, including scheme, port and trailing path.- Refreshing is not automatic. Watch
CurrentExpiresInand callRefreshbefore it lapses, or handle the first 401 and retry. - A DPoP proof is bound to one method and one URL. Reusing a proof across requests fails validation.
- The JWT server and the OAuth2 server both want a
TsgcHTTPServer_Users. Use one shared instance rather than one each, or the two will disagree about who exists. - WebAuthn requires a secure context in the browser, so localhost or HTTPS. It will not run over plain HTTP on a real hostname.
Routing
- Find a component:
reference/components-index.mdlists every component, itsunit, and its edition, grouped by Reg module. - Uses clause: add the component's
unit:value (shown on its API page) to yourusesclause. Nothing compiles without it. - API detail:
reference/api/<Component>.mdhas the Properties, Events and Methods, each in both Delphi and C++Builder form. - Option / enum / event types: property and event types link to
reference/types/<TypeName>.md, which documents the sub-properties of option classes, the values of enums, and the parameter list of event handlers. - Examples:
examples/index.mdis the full demo catalog;examples/<Component>.mdis a focused, real usage snippet for the most-used components. - Concepts:
concepts/overview.md(getting started + uses-clause rule) andconcepts/editions-and-features.md(which components your edition includes). - Bundled resources:
concepts/resources.mdlists the browser-side assets (JavaScript, HTML, CSS) the server components serve or embed, so a browser client works without an external CDN. - Version history:
reference/history.mdlists what changed in each sgcWebSockets release.
Editions
Components are gated by edition (Professional, Enterprise, All-Access) or by a feature define. Check the edition column in the components index, or concepts/editions-and-features.md, before relying on a component.
Only public and published members are documented. Method bodies, private fields and protected members are intentionally not included.