Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Authentication

The gateway provides a robust, built-in Enhanced Authentication Middleware designed for production use. It supports multiple authentication schemes, flexible token validation, and rich user context propagation.

Quick Start

use grpc_graphql_gateway::{
    Gateway, 
    EnhancedAuthMiddleware, 
    AuthConfig, 
    AuthClaims,
    TokenValidator,
    Result
};
use std::sync::Arc;
use async_trait::async_trait;

// 1. Define your token validator
struct MyJwtValidator;

#[async_trait]
impl TokenValidator for MyJwtValidator {
    async fn validate(&self, token: &str) -> Result<AuthClaims> {
        // Implement your JWT validation logic here
        // e.g., decode(token, &decoding_key, &validation)...
        
        Ok(AuthClaims {
            sub: Some("user_123".to_string()),
            roles: vec!["admin".to_string()],
            ..Default::default()
        })
    }
}

// 2. Configure and build the gateway
let auth_middleware = EnhancedAuthMiddleware::new(
    AuthConfig::required()
        .with_scheme(AuthScheme::Bearer),
    Arc::new(MyJwtValidator),
);

let gateway = Gateway::builder()
    .with_descriptor_set_bytes(DESCRIPTORS)
    .add_middleware(auth_middleware)
    .build()?;

Configuration

The AuthConfig builder allows you to customize how authentication is handled:

use grpc_graphql_gateway::{AuthConfig, AuthScheme};

let config = AuthConfig::required()
    // Allow multiple schemes
    .with_scheme(AuthScheme::Bearer)
    .with_scheme(AuthScheme::ApiKey)
    .with_api_key_header("x-service-token")
    
    // Public paths that don't need auth
    .skip_path("/health")
    .skip_path("/metrics")
    
    // Whether to require auth for introspection (default: true)
    .with_skip_introspection(false);

// Or create an optional config (allow unauthenticated requests)
let optional_config = AuthConfig::optional();

Supported Schemes

SchemeDescriptionHeader Example
AuthScheme::BearerStandard Bearer tokenAuthorization: Bearer <token>
AuthScheme::BasicBasic auth credentialsAuthorization: Basic <base64>
AuthScheme::ApiKeyCustom header API keyx-api-key: <key>
AuthScheme::CustomCustom prefixAuthorization: Custom <token>

Token Validation

You can implement the TokenValidator trait for reusable logic, or use a closure for simple cases.

Using a Closure

let auth = EnhancedAuthMiddleware::with_fn(
    AuthConfig::required(),
    |token| Box::pin(async move {
        if token == "secret-password" {
            Ok(AuthClaims {
                sub: Some("admin".to_string()),
                ..Default::default()
            })
        } else {
            Err(Error::Unauthorized("Invalid token".into()))
        }
    })
);

User Context (AuthClaims)

The middleware extracts user information into AuthClaims, which are available in the GraphQL context.

FieldTypeDescription
subOption<String>Subject (User ID)
rolesVec<String>User roles
issOption<String>Issuer
audOption<Vec<String>>Audience
expOption<i64>Expiration (Unix timestamp)
customHashMapCustom claims

Accessing Claims in Resolvers

In your custom resolvers or middleware, you can access these claims via the context:

async fn my_resolver(ctx: &Context) -> Result<String> {
    // Convenience methods
    let user_id = ctx.user_id();     // Option<String>
    let roles = ctx.user_roles();    // Vec<String>
    
    // Check authentication status
    if ctx.get("auth.authenticated") == Some(&serde_json::json!(true)) {
        // ...
    }
    
    // Access full claims
    if let Some(claims) = ctx.get_typed::<AuthClaims>("auth.claims") {
        println!("User: {:?}", claims.sub);
    }
}

Error Handling

  • Missing Token: If AuthConfig::required() is used, returns 401 Unauthorized immediately.
  • Invalid Token: Returns 401 Unauthorized with error details.
  • Expired Token: Automatically checks exp claim and returns 401 if expired.

To permit unauthenticated access (e.g. for public parts of the graph), use AuthConfig::optional(). The request will proceed, but ctx.user_id() will be None.