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