Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 18 additions & 195 deletions website/src/docs/fusion/v16/authentication-and-authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ title: "Authentication and Authorization"

The mental model for auth in Fusion is straightforward: **authentication terminates at the gateway, authorization is a subgraph concern.**

The gateway validates tokens, extracts identity, and forwards the relevant information to subgraphs via HTTP headers. Each subgraph then uses standard HotChocolate authorization (`[Authorize]`, policies, claims) to enforce access control on its own fields. There is nothing Fusion-specific about subgraph authorization -- your subgraph is a HotChocolate server, and you use the same auth patterns you already know.
The gateway validates tokens, extracts identity, and forwards the relevant information to subgraphs via HTTP headers. Each subgraph then enforces access control on its own fields using its own authorization framework.

This page walks through the full auth chain: gateway-level authentication, header propagation, gateway-to-subgraph trust, and subgraph-level authorization.
This page walks through the gateway side of the auth chain: authentication, header propagation, and gateway-to-subgraph trust.

## Gateway-Level Authentication

Expand All @@ -23,15 +23,10 @@ The gateway is the single entry point for all client requests. It is the right p
The most common pattern is JWT (JSON Web Token) authentication. Configure the gateway to validate tokens against your identity provider:

```csharp
// Gateway/Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

// AddServiceDefaults registers shared configuration (OpenTelemetry, health checks, etc.)
// from a shared defaults project. Remove this line if your project does not use shared defaults.
builder.AddServiceDefaults("gateway-api", "1.0.0");

// 1. Register header propagation
builder.Services
.AddCors()
Expand Down Expand Up @@ -82,16 +77,16 @@ app.MapGraphQL();
app.Run();
```

This is standard ASP.NET Core authentication -- the only Fusion-specific parts are `AddGraphQLGateway()` and the header propagation setup (covered next).
This is standard ASP.NET Core authentication. The only Fusion-specific parts are `AddGraphQLGateway()` and the header propagation setup (covered next).

The numbered comments highlight the six key pieces:

1. **Header propagation registration** -- declares which headers the gateway forwards to subgraphs.
2. **Named HTTP client `"fusion"`** -- this is the client the gateway uses to call subgraphs. Adding `.AddHeaderPropagation()` to it ensures the declared headers are forwarded on every subgraph request.
3. **JWT authentication** -- standard ASP.NET Core JWT Bearer setup. Configure `Authority` and `Audience` for your identity provider (Keycloak, Auth0, Azure AD, etc.).
4. **Authorization** -- registers the authorization services.
5. **Fusion gateway** -- registers the gateway with its configuration source.
6. **Middleware order** -- `UseHeaderPropagation()` must come before `UseAuthentication()` so that the `Authorization` header is captured for propagation before the auth middleware consumes it. Then `UseAuthentication()` must come before `UseAuthorization()`.
1. **Header propagation registration.** Declares which headers the gateway forwards to subgraphs.
2. **Named HTTP client `"fusion"`.** This is the client the gateway uses to call subgraphs. Adding `.AddHeaderPropagation()` to it ensures the declared headers are forwarded on every subgraph request.
3. **JWT authentication.** Standard ASP.NET Core JWT Bearer setup. Configure `Authority` and `Audience` for your identity provider (Keycloak, Auth0, Azure AD, etc.).
4. **Authorization.** Registers the authorization services.
5. **Fusion gateway.** Registers the gateway with its configuration source.
6. **Middleware order.** `UseHeaderPropagation()` must come before `UseAuthentication()` so that the `Authorization` header is captured for propagation before the auth middleware consumes it. Then `UseAuthentication()` must come before `UseAuthorization()`.

Comment on lines +86 to 90
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The middleware-order guidance here is internally inconsistent with the later “Custom Header Injection” example that derives propagated headers from HttpContext.User (which is populated by UseAuthentication()). Authentication middleware does not consume/remove the Authorization header, so UseHeaderPropagation() does not need to run before UseAuthentication() to forward it; if anything, when propagating derived headers from claims it should run after authentication (and before MapGraphQL()). Please adjust the wording (and/or the pipeline snippet) to reflect the correct ordering rationale.

Copilot uses AI. Check for mistakes.
### Allowing Anonymous Access

Expand All @@ -104,7 +99,7 @@ builder.Services.AddAuthorization(options =>
});
```

With this configuration, unauthenticated requests pass through the gateway to subgraphs. Individual subgraph fields can then require authentication using `[Authorize]`.
With this configuration, unauthenticated requests pass through the gateway to subgraphs. Individual subgraph fields can then enforce their own authorization rules as needed.

## Header Propagation

Expand Down Expand Up @@ -198,7 +193,7 @@ In a production deployment, subgraphs should not be publicly accessible. The gat
If subgraphs are publicly accessible:

- Clients could bypass the gateway and query subgraphs directly, skipping authentication.
- Internal lookups (marked `[Internal]`) would be exposed -- they are hidden from the composite schema but still exist as real HTTP endpoints on the subgraph.
- Internal lookups (marked `[Internal]`) would be exposed. They are hidden from the composite schema but still exist as real HTTP endpoints on the subgraph.
- Rate limiting, query complexity analysis, and other gateway-level protections are bypassed.

### Network Isolation Options
Expand Down Expand Up @@ -235,176 +230,11 @@ app.Use(async (context, next) =>

In practice, most teams use private networking and do not need additional subgraph-level authentication for internal traffic. Choose the approach that matches your infrastructure and security requirements.

## Subgraph-Level Authorization

Once headers arrive at a subgraph, authorization is entirely standard HotChocolate. There is nothing Fusion-specific here -- your subgraph is a HotChocolate server, and you use the same `[Authorize]` patterns you would use on a standalone GraphQL API.

### Subgraph Auth Setup

Each subgraph configures authentication and authorization in its `Program.cs`:

```csharp
// Products/Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-provider.com/realms/your-realm";
options.Audience = "graphql-api";
options.RequireHttpsMetadata = false; // For local development only
options.TokenValidationParameters = new()
{
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = true
};
});

builder.Services.AddAuthorization();

builder
.AddGraphQL("products-api")
.AddAuthorization() // Enables HotChocolate's [Authorize] attribute
.AddTypes();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();
app.MapGraphQL();

app.RunWithGraphQLCommands(args);
```

Key details:
## Subgraph Authorization

- **`AddGraphQL("products-api")`** is the named schema pattern used in production setups. The string argument is the schema name that matches the subgraph's `schema-settings.json` name. This is equivalent to `AddGraphQLServer()` used in the Getting Started tutorial -- `AddGraphQLServer()` is shorthand for an unnamed schema, while `AddGraphQL("name")` gives the schema an explicit name. Both work the same way for authorization.
- **`.AddAuthorization()`** on the GraphQL builder (not just `builder.Services`) enables HotChocolate's authorization integration, which makes `[Authorize]` work on GraphQL fields and types.
- The JWT configuration can mirror the gateway's configuration, or the subgraph can validate the forwarded `Authorization` header against the same identity provider.
- Subgraphs receive the raw `Authorization` header from the gateway via header propagation, so the JWT middleware validates the same token the gateway already validated.
Authorization is a subgraph concern. Since each subgraph is a standalone GraphQL server, you configure authorization using whatever framework your subgraph is built with. The gateway's role ends at forwarding identity information via headers. How subgraphs enforce access control is up to them.

### Field-Level Authorization

Apply `[Authorize]` to restrict access to specific fields or types:

```csharp
[QueryType]
public static partial class ProductQueries
{
// Anyone can browse products
[Lookup, NodeResolver]
public static async Task<Product?> GetProductByIdAsync(
int id,
IProductByIdDataLoader productById,
CancellationToken cancellationToken)
=> await productById.LoadAsync(id, cancellationToken);

// Only authenticated users can see pricing analytics
[Authorize]
public static PricingAnalytics GetPricingAnalytics(int productId)
=> AnalyticsService.GetForProduct(productId);
}
```

### Policy-Based Authorization

For more granular control, define authorization policies and reference them in `[Authorize]`:

```csharp
// Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
policy.RequireClaim("role", "admin"));

options.AddPolicy("CanManageProducts", policy =>
policy.RequireClaim("permissions", "products:write"));
});
```

```csharp
[MutationType]
public static partial class ProductMutations
{
[Authorize(Policy = "CanManageProducts")]
public static async Task<Product> UpdateProductAsync(
int productId,
string name,
double price,
ProductContext context,
CancellationToken cancellationToken)
{
// Only users with "products:write" permission reach here
var product = await context.Products.FindAsync(productId);
product!.Name = name;
product.Price = price;
await context.SaveChangesAsync(cancellationToken);
return product;
}
}
```

### How Claims and Headers Arrive

When the gateway propagates the `Authorization` header, the subgraph's JWT middleware validates the token and populates `HttpContext.User` with the token's claims. You access claims the same way you would in any ASP.NET Core application:

```csharp
public static UserProfile GetMyProfile(
IHttpContextAccessor httpContextAccessor)
{
var user = httpContextAccessor.HttpContext?.User;
var userId = user?.FindFirst("sub")?.Value;
var email = user?.FindFirst("email")?.Value;
// ...
}
```

If you injected custom headers at the gateway (like `X-User-Id` or `X-Tenant-Id`), read them from the request:

```csharp
var tenantId = httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].ToString();
```

## Common Patterns

### Multi-Tenant Header Propagation

For multi-tenant applications, extract the tenant identifier at the gateway and propagate it as a header so every subgraph can filter data by tenant:

```csharp
// Gateway/Program.cs
builder.Services.AddHeaderPropagation(c =>
{
c.Headers.Add("Authorization");
c.Headers.Add("X-Tenant-Id", context =>
{
// Extract tenant from the JWT token's claims
var tenantId = context.HttpContext.User.FindFirst("tenant_id")?.Value
?? "default";
return new StringValues(tenantId);
});
});
```

Each subgraph reads the tenant header and applies it as a data filter:

```csharp
// Subgraph middleware or resolver
public static async Task<List<Product>> GetProducts(
IHttpContextAccessor httpContextAccessor,
ProductContext context,
CancellationToken cancellationToken)
{
var tenantId = httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].ToString();
return await context.Products
.Where(p => p.TenantId == tenantId)
.ToListAsync(cancellationToken);
}
```
For subgraphs built with HotChocolate, see the [HotChocolate Authorization docs](/docs/hotchocolate/v16/securing-your-api/authorization) for a complete guide on field-level authorization, policies, and claims-based access control.

### What Happens When Auth Fails Mid-Query

Expand All @@ -431,7 +261,7 @@ When a query touches multiple subgraphs and authorization fails on one field, th
}
```

This is standard GraphQL error behavior -- the unauthorized field returns `null`, and the rest of the query succeeds. Design your schema accordingly: fields that might be unauthorized should be nullable so the query can still return useful data even when some fields are denied.
This is standard GraphQL error behavior. The unauthorized field returns `null`, and the rest of the query succeeds. Design your schema accordingly: fields that might be unauthorized should be nullable so the query can still return useful data even when some fields are denied.

### The Full Auth Middleware Chain

Expand Down Expand Up @@ -484,15 +314,8 @@ app.MapGraphQL(); // Serve the GraphQL endpoint
app.Run();
```

### Known Issues

**`ApplyPolicy` type name collision ([GitHub #6333](https://github.com/ChilliCream/graphql-platform/issues/6333)):** When both the gateway and subgraphs register authorization, the `ApplyPolicy` enum type can collide during composition. The workaround is to ensure authorization configuration is consistent across all services. This issue is tracked and being addressed.

**Header propagation with `InitializeOnStartup` ([GitHub #5547](https://github.com/ChilliCream/graphql-platform/issues/5547)):** In some configurations, header propagation may not work correctly when the gateway initializes eagerly. If you encounter missing headers on subgraph requests, verify that `UseHeaderPropagation()` appears before `UseAuthentication()` in the middleware pipeline, and that the `"fusion"` HTTP client has `.AddHeaderPropagation()` attached.

## Next Steps

- **"I need to deploy this securely"** -- [Deployment & CI/CD](/docs/fusion/v16/deployment-and-ci-cd) covers production deployment patterns including network isolation and CI pipeline setup.
- **"I need to handle subgraph failures gracefully"** -- Error handling and resilience, including partial results and retry policies, will be covered in future documentation.
- **"I want to monitor auth-related issues"** -- Monitoring and observability, including distributed tracing across gateway and subgraphs, will be covered in future documentation.
- **"Something is broken"** -- Check the middleware order section above and ensure `UseHeaderPropagation()` appears before `UseAuthentication()` in the pipeline.
- **"I need to set up subgraph authorization."** The [HotChocolate Authorization docs](/docs/hotchocolate/v16/securing-your-api/authorization) cover field-level authorization, policies, and claims-based access control for HotChocolate subgraphs.
- **"I need to deploy this securely."** [Deployment & CI/CD](/docs/fusion/v16/deployment-and-ci-cd) covers production deployment patterns including network isolation and CI pipeline setup.
- **"Something is broken."** Check the middleware order section above and ensure `UseHeaderPropagation()` appears before `UseAuthentication()` in the pipeline.
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This “Something is broken” troubleshooting bullet repeats the earlier (and potentially incorrect) requirement that UseHeaderPropagation() must appear before UseAuthentication(). Since derived propagated headers (e.g., X-User-Id) require HttpContext.User to be populated, the recommended order should be updated/nuanced (e.g., UseAuthentication() before UseHeaderPropagation() when deriving headers from claims, while raw Authorization forwarding does not require a strict order).

Suggested change
- **"Something is broken."** Check the middleware order section above and ensure `UseHeaderPropagation()` appears before `UseAuthentication()` in the pipeline.
- **"Something is broken."** Check the middleware order section above and verify the order matches your propagation strategy: for raw header forwarding such as `Authorization`, there is no strict requirement that `UseHeaderPropagation()` run before `UseAuthentication()`, but for propagated headers derived from authenticated user claims, `UseAuthentication()` must run before `UseHeaderPropagation()` so `HttpContext.User` is populated.

Copilot uses AI. Check for mistakes.
Loading