diff --git a/samples/LocalWorkers/Trax.Samples.GameServer.Api/GoogleJwtResolver.cs b/samples/LocalWorkers/Trax.Samples.GameServer.Api/GoogleJwtResolver.cs index 4ab4d0a..abf6418 100644 --- a/samples/LocalWorkers/Trax.Samples.GameServer.Api/GoogleJwtResolver.cs +++ b/samples/LocalWorkers/Trax.Samples.GameServer.Api/GoogleJwtResolver.cs @@ -5,15 +5,43 @@ namespace Trax.Samples.GameServer.Api; /// -/// Maps a Google-issued id-token onto a Trax principal. The JWT bearer -/// handler has already verified signature, issuer, audience, and lifetime -/// against Google's JWKS before this runs. +/// Sample-only resolver that grants every authenticated Google user the +/// Player role so the GameServer sample trains can be exercised by +/// anyone who can sign in. You almost certainly don't need a class like +/// this in a real app. /// /// -/// Demo only: every authenticated Google user is granted the Player role so -/// sample trains work out of the box. A real deployment would look up the -/// principal in the game's user table and assign roles based on account -/// state, revoke unknown subjects, etc. +/// +/// When you don't need a custom resolver. For most apps, the one-line +/// call services.AddTraxJwtAuth(authority, audience) is the whole +/// integration. Trax's DefaultJwtPrincipalResolver already maps the +/// standard OIDC claims (sub, name, preferred_username, +/// email, role/roles) onto a TraxPrincipal. Any +/// provider that emits standard claims works without additional code. +/// +/// +/// When a custom resolver IS justified. Reach for this only when one +/// of the following is true: +/// +/// Roles live in a non-standard claim the default mapper doesn't +/// recognize (e.g. tenant-specific Entra app roles or Okta group URIs). +/// You need to enrich the principal from your own database — fetch +/// the user's tenant, permissions, feature flags, etc. +/// You need to reject unknown subjects: allow-list of provisioned +/// users, revocation cache, suspension check. +/// +/// In those cases the pattern is exactly what's below: implement +/// ITraxPrincipalResolver<JwtTokenInput>, read claims off +/// input.Principal (already signature/issuer/audience/lifetime +/// validated by JwtBearerHandler), and return either a +/// TraxPrincipal or null to reject. +/// +/// +/// This sample uses a custom resolver PURELY for the role-assignment +/// hack — hardcoded Player so the trains work out of the box. +/// Remove the generic overload in Program.cs and the default resolver +/// will do the rest. +/// /// internal sealed class GoogleJwtResolver : ITraxPrincipalResolver { diff --git a/samples/LocalWorkers/Trax.Samples.GameServer.Api/Program.cs b/samples/LocalWorkers/Trax.Samples.GameServer.Api/Program.cs index 81a1355..71c3c9b 100644 --- a/samples/LocalWorkers/Trax.Samples.GameServer.Api/Program.cs +++ b/samples/LocalWorkers/Trax.Samples.GameServer.Api/Program.cs @@ -16,8 +16,15 @@ // companion app (samples/LocalWorkers/trax-samples-gameserver-web) // signs users in with Google via NextAuth, then forwards the id-token // to this API. Trax validates against Google's JWKS. Enable by setting -// Google:ClientId in appsettings.json to your OAuth 2.0 client id from -// Google Cloud Console. +// Google:ClientId (via `dotnet user-secrets set "Google:ClientId" `) +// to your OAuth 2.0 client id from Google Cloud Console. +// +// For a real app, the whole integration is one line: +// services.AddTraxJwtAuth("https://accounts.google.com", googleClientId); +// The default resolver handles standard OIDC claims. This sample uses a +// custom resolver only to hand every Google user the Player role so the +// trains work out of the box — see GoogleJwtResolver.cs for the rationale +// and the "when do I need a custom resolver?" guidance. // // Both schemes feed the same TraxPrincipal, so [TraxAuthorize] works against // either credential type. @@ -125,6 +132,16 @@ // them via NextAuth and sends them as Authorization: Bearer . // Signature validation happens against Google's published JWKS; only tokens // minted for our specific OAuth client id are accepted (aud claim check). +// +// The one-line path below is enough for most apps — the package's default +// resolver maps sub → Id, name/email → DisplayName, and role claims → Roles. +// We override with GoogleJwtResolver here ONLY because this sample needs to +// grant every signed-in user the Player role so the sample trains work. +// A real deployment would keep the default and look up roles in its user +// database via a scoped resolver — see GoogleJwtResolver.cs for the shape. +// +// Default-resolver version (swap in for a real app): +// builder.Services.AddTraxJwtAuth("https://accounts.google.com", googleClientId); var googleClientId = builder.Configuration["Google:ClientId"]; if (!string.IsNullOrWhiteSpace(googleClientId)) { diff --git a/samples/LocalWorkers/trax-samples-gameserver-web/README.md b/samples/LocalWorkers/trax-samples-gameserver-web/README.md index f9823e0..6a979f3 100644 --- a/samples/LocalWorkers/trax-samples-gameserver-web/README.md +++ b/samples/LocalWorkers/trax-samples-gameserver-web/README.md @@ -5,9 +5,12 @@ with Google via NextAuth, then forwards the Google-issued id-token to the Trax GraphQL API as an `Authorization: Bearer` credential. The API validates the token against Google's JWKS via `AddTraxJwtAuth("https://accounts.google.com", googleClientId)`. -> **Demo only.** The resolver in `GoogleJwtResolver.cs` grants the `Player` -> role to every authenticated Google user so the sample's trains work out of -> the box. Do not copy that pattern into anything real. +> **Most apps don't need a custom resolver.** The actual integration is one +> line — `services.AddTraxJwtAuth("https://accounts.google.com", clientId)` — +> and Trax's default resolver handles standard OIDC claims. This sample only +> has a `GoogleJwtResolver` class because it hard-assigns the `Player` role +> to every signed-in user so the trains are exercisable; see the comments on +> that class for "when do I actually need a custom resolver?" ## Prerequisites