> ## Documentation Index
> Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Framework examples

> Code examples for reading the Sentinel Principal object in Hono, Next.js, Express, and Go. Parse the forwarded identity in your framework.

<Info>
  Unkey Deploy is in public beta. To try it, open the product switcher in the
  top-left of the dashboard and select **Deploy**. During beta, deployed
  resources are free. We're eager for feedback, so let us know what you think
  on [Discord](https://unkey.com/discord), [X](https://x.com/unkeydev), or
  email [support@unkey.com](mailto:support@unkey.com).
</Info>

These examples show how to parse the [Principal](/platform/sentinel/principal/overview) from the `X-Unkey-Principal` header in common frameworks using API key authentication.

<CodeGroup>
  ```ts Hono theme={"theme":"kanagawa-wave"}
  import { Hono } from "hono";

  type KeyPrincipal = {
    version: string;
    subject: string;
    type: "API_KEY";
    identity?: {
      externalId: string;
      meta: Record<string, unknown>;
    };
    source: {
      key: {
        keyId: string;
        keySpaceId: string;
        name?: string;
        expiresAt?: number;
        meta: Record<string, unknown>;
        roles?: string[];
        permissions?: string[];
      };
    };
  };

  const app = new Hono();

  app.use("*", async (c, next) => {
    const header = c.req.header("x-unkey-principal");
    if (!header) {
      return c.json({ error: "Not authenticated" }, 401);
    }

    const principal: KeyPrincipal = JSON.parse(header);
    c.set("principal", principal);
    await next();
  });

  app.get("/api/resource", (c) => {
    const principal = c.get("principal") as KeyPrincipal;

    if (!principal.source.key.permissions?.includes("api.read")) {
      return c.json({ error: "Insufficient permissions" }, 403);
    }

    const userId = principal.subject;
    const org = principal.identity?.meta?.org;

    return c.json({ userId, org });
  });

  export default app;
  ```

  ```ts Next.js theme={"theme":"kanagawa-wave"}
  import { NextRequest, NextResponse } from "next/server";

  type KeyPrincipal = {
    version: string;
    subject: string;
    type: "API_KEY";
    identity?: {
      externalId: string;
      meta: Record<string, unknown>;
    };
    source: {
      key: {
        keyId: string;
        keySpaceId: string;
        name?: string;
        expiresAt?: number;
        meta: Record<string, unknown>;
        roles?: string[];
        permissions?: string[];
      };
    };
  };

  function getPrincipal(req: NextRequest): KeyPrincipal | null {
    const header = req.headers.get("x-unkey-principal");
    if (!header) {
      return null;
    }
    return JSON.parse(header) as KeyPrincipal;
  }

  export async function GET(req: NextRequest) {
    const principal = getPrincipal(req);
    if (!principal) {
      return NextResponse.json(
        { error: "Not authenticated" },
        { status: 401 }
      );
    }

    const userId = principal.subject;
    const plan = principal.identity?.meta?.plan ?? "free";
    const canWrite = principal.source.key.permissions?.includes("api.write") ?? false;

    return NextResponse.json({ userId, plan, canWrite });
  }
  ```

  ```ts Express theme={"theme":"kanagawa-wave"}
  import express, { Request, Response, NextFunction } from "express";

  type KeyPrincipal = {
    version: string;
    subject: string;
    type: "API_KEY";
    identity?: {
      externalId: string;
      meta: Record<string, unknown>;
    };
    source: {
      key: {
        keyId: string;
        keySpaceId: string;
        name?: string;
        expiresAt?: number;
        meta: Record<string, unknown>;
        roles?: string[];
        permissions?: string[];
      };
    };
  };

  declare global {
    namespace Express {
      interface Request {
        principal?: KeyPrincipal;
      }
    }
  }

  const app = express();

  app.use((req: Request, res: Response, next: NextFunction) => {
    const header = req.header("x-unkey-principal");
    if (!header) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    req.principal = JSON.parse(header) as KeyPrincipal;
    next();
  });

  app.get("/api/resource", (req: Request, res: Response) => {
    const principal = req.principal!;

    if (!principal.source.key.permissions?.includes("api.read")) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }

    const userId = principal.subject;
    const plan = principal.identity?.meta?.plan ?? "free";

    res.json({ userId, plan });
  });

  app.listen(8080);
  ```

  ```go Go theme={"theme":"kanagawa-wave"}
  package main

  import (
  	"encoding/json"
  	"net/http"
  	"slices"
  )

  type KeyPrincipal struct {
  	Version  string    `json:"version"`
  	Subject  string    `json:"subject"`
  	Type     string    `json:"type"`
  	Identity *Identity `json:"identity,omitempty"`
  	Source   struct {
  		Key KeySource `json:"key"`
  	} `json:"source"`
  }

  type Identity struct {
  	ExternalID string         `json:"externalId"`
  	Meta       map[string]any `json:"meta"`
  }

  type KeySource struct {
  	KeyID       string         `json:"keyId"`
  	KeySpaceID  string         `json:"keySpaceId"`
  	Name        string         `json:"name,omitempty"`
  	ExpiresAt   int64          `json:"expiresAt,omitempty"` // unix milliseconds
  	Meta        map[string]any `json:"meta"`
  	Roles       []string       `json:"roles,omitempty"`
  	Permissions []string       `json:"permissions,omitempty"`
  }

  func parsePrincipal(r *http.Request) (*KeyPrincipal, bool) {
  	header := r.Header.Get("X-Unkey-Principal")
  	if header == "" {
  		return nil, false
  	}

  	var p KeyPrincipal
  	if err := json.Unmarshal([]byte(header), &p); err != nil {
  		return nil, false
  	}
  	return &p, true
  }

  func handler(w http.ResponseWriter, r *http.Request) {
  	principal, ok := parsePrincipal(r)
  	if !ok {
  		http.Error(w, `{"error":"Not authenticated"}`, http.StatusUnauthorized)
  		return
  	}

  	// Permissions is nil when the key has no permissions attached.
  	// slices.Contains on a nil slice returns false without panicking.
  	if !slices.Contains(principal.Source.Key.Permissions, "api.read") {
  		http.Error(w, `{"error":"Insufficient permissions"}`, http.StatusForbidden)
  		return
  	}

  	userID := principal.Subject
  	json.NewEncoder(w).Encode(map[string]string{"userId": userID})
  }

  func main() {
  	http.HandleFunc("/api/resource", handler)
  	http.ListenAndServe(":8080", nil)
  }
  ```
</CodeGroup>
