Reference

Startup refusals

Source Startup/, ExposureAuthorizationRule.cs, TypeExtensionExposureRule.cs, TraxGraphQLBuilder.Build.cs, TraxGraphQLBuilder.Operations.cs, TraxGraphQLAuthPolicyValidator.cs, DemoApiKeyEnvironmentValidator.cs, DemoJwtKeyEnvironmentValidator.cs, AuthorizationRegistrationValidator.cs, ApiDashboardEndpointExtensions.cs, TraxApiDashboardOptions.cs

A Trax host whose security posture is missing or contradictory does not start. Each refusal is an InvalidOperationException whose message names the offending type, field, policy or call and the change that answers it. Nothing on this page is a warning: the host stops.

A refusal is raised at one of these points:

PointWhat runs there
The builder callThe method itself throws while Program.cs registers services, before builder.Build().
AddTraxGraphQLThe GraphQL builder's Build() and the train census, while AddTraxGraphQL(...) runs.
Host startA hosted service checks the built container and schema in StartingAsync, before the host serves a request.
UseTraxApiDashboardThe operations console checks its posture while it is mapped, and once more as the endpoint is built at host start.

Exposed surfaces

A surface is exposed when it is a [TraxQuery] or [TraxMutation] train, a [TraxBroadcast] train, a [TraxQueryModel] entity, an entity a query model reaches, or a field a type extension adds. The rule behind these refusals is Required exposure posture.

CauseRaisedMessage beginsAnswer
An exposed train with neither [TraxAuthorize] nor [TraxAllowAnonymous], on an endpoint without RequireAuthorization()AddTraxGraphQLTrax GraphQL exposure authorization check failed:, then one line per train: GraphQL-exposed train '<type>' is exposed via GraphQL but declares neither ... ([TraxBroadcast] train for a broadcast-only one)Add one of the two markers, or gate the endpoint
A [TraxQueryModel] entity with neither marker, on an endpoint without RequireAuthorization()AddTraxGraphQL[TraxQueryModel] entity '<type>' is exposed via GraphQL but declares neither ...Add one of the two markers, or gate the endpoint
Both markers on one train or entity, directly or through a base class or interfaceAddTraxGraphQL... '<type>' declares both [TraxAllowAnonymous] and [TraxAuthorize] ...Remove one
[TraxAllowAnonymous] on an exposed train or entity while the builder calls RequireAuthorization()AddTraxGraphQL... '<type>' declares [TraxAllowAnonymous], but the GraphQL endpoint is gated via RequireAuthorization(). ...Remove the attribute, or the endpoint gate
[TraxAuthorize] with an empty or whitespace Policy, or a Roles list that holds no role, on a query modelAddTraxGraphQL[TraxAuthorize] on '<type>' has an empty or whitespace Policy value. or [TraxAuthorize(Roles="...")] on '<type>' parsed to zero roles ...Name a policy or a role, or drop the argument
The same, on a train or one of its interfacesHost startAs aboveAs above
An entity a query model reaches (a navigation, or a method returning an entity) that is not a query model and declares neither marker, on an endpoint without RequireAuthorization()Host start<n> entit(y/ies) reached from a [TraxQueryModel] declare no usable authorization posture ..., naming every schema coordinate that reaches itA marker on the entity class, the navigation left out of the model's field set (BindFields = Explicit or ExposeAs), or an endpoint gate
A field a type extension adds to a parent that gives it no gate (a [TraxAllowAnonymous] type, a root type, a namespace reached from the root, the operations namespace under AllowAnonymousOperations()), with no marker of its ownHost start<n> GraphQL field(s) or type extension(s) cannot be served as declared:[TraxAuthorize] or [TraxAllowAnonymous] on the resolver method or the extension class. See Fields added by a type extension
Both markers on such a fieldHost startAs aboveRemove one
HotChocolate's [Authorize] or [AllowAnonymous] on a field a type extension adds, or on the extension classHost startAs above, then GraphQL field '<Type.field>' (<resolver>) declares its authorization posture with [Authorize], which Trax does not read. ...[TraxAuthorize] or [TraxAllowAnonymous]
The @authorize directive Trax put on a gated query model, its entry field or a gated reached entity is missing from the built schema, or one is present on a [TraxAllowAnonymous] entityHost start... ConfigureSchema callback has ...Remove the ConfigureSchema override that replaced the type or directive

Policies

CauseRaisedMessage beginsAnswer
RequireAuthorization(policy) names a policy that is not registered, or RequireAuthorization() is called and no AddTrax*Auth call contributed the combined Trax policyHost startAddTraxGraphQL(graphql => graphql.RequireAuthorization(...)) was called, but the authorization policy '<name>' is not registered.Register the policy, or an auth scheme
A train's [TraxAuthorize] names a policy that is not registeredHost start<n> authorization polic(y is/ies are) named by a train but not registered:AddAuthorization(o => o.AddPolicy("<name>", ...)), or correct the name
A query model's [TraxAuthorize] names a policy that is not registeredHost start... references an authorization policy that is not registered.As above
GateOperations(policy: ...), a type extension's [TraxAuthorize], or any other @authorize directive in the built schema names a policy that is not registeredHost start... named in the GraphQL schema but not registered:As above

Roles are never checked at startup: Trax has no list of the roles that can exist.

The operations namespace

CauseRaisedMessage beginsAnswer
ExposeOperationQueries() or ExposeOperationMutations() with no gate: no GateOperations(...), no RequireAuthorization(), no AllowAnonymousOperations()AddTraxGraphQLExposeOperationMutations() exposes scheduler-control mutations ... but the GraphQL endpoint is not gated ... (ExposeOperationQueries() exposes the scheduler control plane's read surface ... when only the queries are exposed)GateOperations(policy, roles), RequireAuthorization(), or AllowAnonymousOperations()
GateOperations() with neither a policy nor rolesThe builder callGateOperations() needs a policy or roles: ...Name a policy or roles, or call GateOperationsToAuthenticatedUsers()
GateOperations(roles: ...) with a list that names no role (",")The builder callGateOperations(roles: "<list>") names no role: ...Name one or more roles
GateOperations(...) with the namespace not exposedAddTraxGraphQLGateOperations() was called but the operations namespace is not exposed ...Expose it, or remove the call
AllowAnonymousOperations() with RequireAuthorization()AddTraxGraphQLAllowAnonymousOperations() was called together with RequireAuthorization(). ...Remove AllowAnonymousOperations()
AllowAnonymousOperations() with GateOperations(...)AddTraxGraphQLAllowAnonymousOperations() was called together with GateOperations(). ...Remove one
The namespace exposed without IOperationsServiceHost startAddTraxGraphQL() exposes the operations surface (ExposeOperationQueries / ExposeOperationMutations), but IOperationsService is not registered, ...AddScheduler(...) on the same host
The namespace exposed without the mediator's ITrainExecutionServiceHost startAddTraxGraphQL() exposes the operations surface, but ITrainExecutionService is not registered, ...AddMediator(...)
The mutations exposed without ITraxScheduler or without an IJobSubmitterHost startAddTraxGraphQL() exposes the operations mutations (ExposeOperationMutations), but ITraxScheduler is not registered, ..., or ... but no IJobSubmitter is registered, ...AddScheduler(...)

Train authorization

CauseRaisedMessage beginsAnswer
A registered train carries [TraxAuthorize] and no ITrainAuthorizationService is registeredHost startOne or more registered trains declare [TraxAuthorize] but no ITrainAuthorizationService is registered in the DI container. ..., ending with the trainsAddTraxGraphQL() or AddTraxApi() registers the default; a host that serves no submissions calls AllowMissingAuthorizationService()
A junction takes TraxPrincipal in its constructor and nothing registers it (every AddTrax*Auth call was skipped)Host start<n> of <m> registered trains cannot run:, naming the junction and Trax.Api.Auth.TraxPrincipalservices.AddTraxPrincipalAccessor(). See Injecting TraxPrincipal

Credentials

CauseRaisedMessage beginsAnswer
An API key containing do-not-use-in-production registered with keys.Add(...), outside DevelopmentHost startAddTraxApiKeyAuth() registered a key containing 'do-not-use-in-production', which marks a published demo key, ...Register demo keys only inside IsDevelopment(); elsewhere AddHashed(...) or a resolver
A JWT bearer scheme whose signing key contains do-not-use-in-production, outside Development, however the key was setHost startThe JWT bearer scheme '<scheme>' validates tokens with a signing key containing 'do-not-use-in-production', ...Load the key from a secret store, or UseAuthority(...)

A key fetched from an authority's JWKS, or produced by an IssuerSigningKeyResolver, does not exist at startup and is not checked.

Subscriptions

CauseRaisedMessage beginsAnswer
The socket interceptor for the Trax schema has been replaced by HotChocolate's default, which accepts every connection_initHost start<scheme> is registered, but the subscription socket interceptor for the Trax schema is HotChocolate's default, which accepts every connection_init. ...Remove the replacement, or register your own interceptor type with ConfigureSchema(b => b.AddSocketSessionInterceptor<T>())

The operations console

CauseRaisedMessage beginsAnswer
No posture: no RequirePolicy(...), RequireRoles(...) or AllowAnonymousShell()UseTraxApiDashboardUseTraxApiDashboard() needs to know who may load the dashboard, ...Choose one
AllowAnonymousShell() together with RequirePolicy(...) or RequireRoles(...)The options callAllowAnonymousShell() and RequirePolicy()/RequireRoles() contradict each other: ...Remove one
AllowAnonymousShell() together with HostAuthenticates()The options callAllowAnonymousShell() and HostAuthenticates() contradict each other: ...Remove one
RequirePolicy(name) names a policy that is not registeredUseTraxApiDashboardThe dashboard requires the authorization policy '<name>', which is not registered. ...Register the policy
The console is gated but no authentication scheme can challenge or forbid a refused callerUseTraxApiDashboardThe dashboard requires authorization, but the host has no authentication scheme ..., or names the missing schemesRegister a scheme, or name it on the policy with AddAuthenticationSchemes(...)
AllowAnonymous() chained on the returned builder over a policy or roles postureHost startThe Trax API dashboard at <path> was given AllowAnonymous(), which removes its RequirePolicy()/RequireRoles() posture ...Remove AllowAnonymous(), or use AllowAnonymousShell()

UseTraxApiDashboard documents the options these refer to.

Not refused at startup

  • A host with no AddTrax*Auth call at all and no endpoint gate. Every gated surface then refuses every caller with TRAX_AUTHORIZATION, and only [TraxAllowAnonymous] surfaces answer.
  • A train that is not exposed (no [TraxQuery], [TraxMutation] or [TraxBroadcast]) and carries no marker. It is not reachable over GraphQL.
  • A request that meets an @authorize directive naming an unknown policy on a host that never ran its startup checks. The caller gets TRAX_AUTHORIZATION with the generic message, and the policy name goes to the log.

A remote worker's entry points refuse to start without an authorization posture of their own; see Remote execution.

See also