# Framework examples

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

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

<CodeGroup>

```ts Hono
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;
      credits?: 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
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;
      credits?: 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
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;
      credits?: 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
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
	Credits     *int64         `json:"credits,omitempty"`   // remaining, nil when unlimited
	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>
