Organization Auth
Introduction
Organization-scoped authentication enables server-side requests authenticated at the organization level rather than the user level. This authentication method is essential for operations that require authentication but cannot use user-scoped auth (typically when users are unauthenticated or don't have accounts yet).
Unlike user-scoped authentication which uses individual user credentials, organization-scoped auth uses a shared organization access token to perform operations on behalf of the organization. This prevents sensitive data from being publicly accessible while still allowing necessary operations for unauthenticated users.
- Server-side only: Strictly for SSR, API handlers, and Server Actions. Never exposed client-side
- Cached tokens: Encrypted tokens stored in Vercel Edge Config for high performance
- Automatic refresh: Automatic token refresh cron job using a cron job
- Fallback mechanism: Direct API calls if cached tokens are invalid
Getting started
This is an advanced authentication feature. Complete the Getting Started: Pages Router or Getting Started: App Router guide first.
Configuration
Organization-scoped authentication requires additional configuration beyond basic auth setup, including Vercel Edge Config integration and token encryption.
See Vercel's Edge Config documentation for setup instructions.
Environment variables
Add these environment variables to your .env.local file and configure them in
your Vercel project settings:
# Organization authentication (Kraken > API Tools > API organisations)
KRAKEN_ORGANIZATION_KEY="your-organization-secret-key"
# Vercel Edge Config (automatically configured by Vercel)
EDGE_CONFIG="https://edge-config.vercel.com/..."
# Vercel API access token (create at https://vercel.com/account/tokens)
VERCEL_AUTH_TOKEN="your-edge-config-access-token"
# Vercel team ID
VERCEL_TEAM_ID="your-vercel-team-id"
# Token encryption (key hashed to 256-bit, IV should be 12+ bytes)
AUTH_ENCRYPTION_KEY="your-encryption-key"
AUTH_ENCRYPTION_IV="your-encryption-iv"
# Cron job authentication (16 characters minimum)
CRON_SECRET="your-cron-secret"
Generate strong random values for encryption keys and secrets:
# Encryption key
openssl rand -base64 32
# Initialization vector
openssl rand -base64 16
# Cron secret
openssl rand -base64 16
Treat these values as sensitive credentials and never commit them to version control.
Auth configuration
createAuthConfig automatically picks up the Edge Config, encryption, and
Kraken configuration from environment variables. As long as the environment
variables are set correctly, organization-scoped auth will work without
additional config changes.
Server functions
You can use getOrganizationScopedGraphQLClient either directly from the
package or using a factory function:
- Pages Router
- App Router
- Direct Import
Export the function from your server auth utilities using the factory:
import { createServerSideAuth } from "@krakentech/blueprint-auth/server";
import { authConfig } from "./config";
export const { getOrganizationScopedGraphQLClient } =
createServerSideAuth(authConfig);
Export the function from your server auth utilities using the App Router factory:
"use server";
import { createAppRouterAuth } from "@krakentech/blueprint-auth/server";
import { cookies, headers } from "next/headers";
import { authConfig } from "./config";
export const { getOrganizationScopedGraphQLClient } = createAppRouterAuth(
authConfig,
{ cookies, headers },
);
Import the function directly from the package:
import { getOrganizationScopedGraphQLClient } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
// Use it with explicit config
const graphQLClient = getOrganizationScopedGraphQLClient({
context,
...authConfig,
});
Token refresh cron job
Organization tokens expire after 60 minutes. Set up a cron job to refresh tokens every 55 minutes to ensure they remain valid.
API handler
- Pages Router
- App Router
import { createUpdateOrgTokenHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export default createUpdateOrgTokenHandler(authConfig);
import { createUpdateOrgTokenHandler } from "@krakentech/blueprint-auth/server";
import { authConfig } from "@/lib/auth/config";
export const GET = createUpdateOrgTokenHandler(authConfig);
Vercel cron configuration
Create or update vercel.json at the root of your project:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"crons": [
{
"path": "/api/auth/update-org-token",
"schedule": "*/55 * * * *"
}
]
}
The */55 * * * * schedule runs every 55 minutes. Since organization tokens
typically expire after 60 minutes, this provides a 5-minute buffer to ensure
tokens are always valid.
The cron job handler validates requests using the CRON_SECRET environment
variable. Vercel automatically includes this secret in cron job requests. Ensure
CRON_SECRET is set in your environment variables.
Usage
Organization-scoped authentication is for server-side use only.
Queries
Use organization-scoped auth when fetching data that isn't publicly accessible for unauthenticated users.
- Pages Router
- App Router
import type {
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import { getOrganizationScopedGraphQLClient } from "@/lib/auth/server";
import { graphql } from "@/lib/graphql";
import { ProductCard } from "@/components/ProductCard";
export default function ProductsPage({
products,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<div>
<h1>Products</h1>
{products.map((product) => (
<ProductCard key={product.code} product={product} />
))}
</div>
);
}
const ProductsQuery = graphql(`
query SignupProducts() {
products(availability: AVAILABLE) {
code
fullName
description
tariffs {
id
standingCharge
unitRate
}
}
}
`);
export async function getServerSideProps(context: GetServerSidePropsContext) {
const graphQLClient = getOrganizationScopedGraphQLClient({
context,
});
const { products } = await graphQLClient.request(ProductsQuery);
if (!products?.length) {
return { notFound: true };
}
return {
props: { products },
};
}
import { Suspense } from "react";
import { getOrganizationScopedGraphQLClient } from "@/lib/auth/server";
import { graphql } from "@/lib/graphql";
import { notFound } from "next/navigation";
import { ProductCard } from "@/components/ProductCard";
const ProductsQuery = graphql(`
query SignupProducts {
products(availability: AVAILABLE) {
code
fullName
description
tariffs {
id
standingCharge
unitRate
}
}
}
`);
async function Products() {
const graphQLClient = getOrganizationScopedGraphQLClient();
const { products } = await graphQLClient.request(ProductsQuery);
if (!products?.length) {
notFound();
}
return products.map((product) => (
<ProductCard key={product.code} product={product} />
));
}
export default function ProductsPage() {
return (
<div>
<h1>Products</h1>
<Suspense fallback={<div>Loading products...</div>}>
<Products />
</Suspense>
</div>
);
}
Mutations
Use organization-scoped auth for mutations that create or modify data for users who aren't authenticated yet. A common example is account creation.
- Pages Router
- App Router
import type { NextApiRequest, NextApiResponse } from "next";
import { getOrganizationScopedGraphQLClient } from "@/lib/auth/server";
import { graphql } from "@/lib/graphql";
import { z } from "zod";
const CreateAccountMutation = graphql(`
mutation CreateAccount($input: CreateAccountInput!) {
createAccount(input: $input) {
account {
id
number
}
}
}
`);
const createAccountSchema = z.object({
email: z.string().email(),
// ... other fields
});
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const result = createAccountSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: "Invalid request data" });
}
const {
email,
// ... other fields
} = result.data;
try {
// User doesn't have an account yet, use organization-scoped auth
const graphQLClient = getOrganizationScopedGraphQLClient({
context: { req, res },
});
const { createAccount } = await graphQLClient.request(
CreateAccountMutation,
{
input: {
email,
// ... other fields
},
},
);
return res.redirect(
303,
`/dashboard/accounts/${createAccount.account.number}`,
);
} catch (error) {
console.error("Account creation failed:", error);
return res.status(500).json({ error: "Account creation failed" });
}
}
"use server";
import { getOrganizationScopedGraphQLClient } from "@/lib/auth/server";
import { graphql } from "@/lib/graphql";
import { redirect } from "next/navigation";
import { z } from "zod";
const CreateAccountMutation = graphql(`
mutation CreateAccount($input: CreateAccountInput!) {
createAccount(input: $input) {
account {
id
number
}
}
}
`);
const createAccountSchema = z.object({
email: z.string().email(),
// ... other fields
});
type CreateAccountState = {
error?: string;
};
export async function createAccount(
prevState: CreateAccountState | null,
formData: FormData,
): Promise<CreateAccountState> {
const rawData = {
email: formData.get("email"),
// ... other fields
};
// Validate form data
const result = createAccountSchema.safeParse(rawData);
if (!result.success) {
return {
error: "Invalid form data",
};
}
const {
email,
// ... other fields
} = result.data;
try {
const graphQLClient = getOrganizationScopedGraphQLClient();
const { createAccount } = await graphQLClient.request(
CreateAccountMutation,
{
input: {
email,
// ... other fields
},
},
);
redirect(`/dashboard/accounts/${createAccount.account.number}`);
} catch (error) {
console.error("Account creation failed:", error);
return {
error: "Account creation failed",
};
}
}
How it works
Organization-scoped auth uses a cached token system to provide high performance while maintaining security:
- Token Storage: Access tokens are encrypted using AES-256-GCM and stored in Edge Config.
- Token Retrieval:
getOrganizationScopedGraphQLClient()retrieves cached tokens from Edge Config - Automatic Refresh: If a token is expired or invalid, the client automatically fetches a fresh token
- Cron Refresh: A cron job refreshes tokens to ensure a valid token is always available
- Edge Config Updates: New tokens are encrypted and written back to Edge Config, with automatic cache invalidation across all edge regions
This architecture ensures sub-millisecond token retrieval in most cases while gracefully handling token expiration and rotation.
Security considerations
- Environment variables: Treat
KRAKEN_ORGANIZATION_KEY,AUTH_ENCRYPTION_KEY,AUTH_ENCRYPTION_IV, andCRON_SECRETas highly sensitive credentials - Cron endpoint protection: The token refresh endpoint is protected by
CRON_SECRET. Never expose this value - Token encryption: All cached tokens are encrypted before storage in Edge Config
- Minimal scope: Organization tokens grant broader permissions than user tokens. Use user-scoped auth whenever possible
FAQ
When should I use organization-scoped vs user-scoped auth?
Use organization-scoped auth when:
- Users are unauthenticated but need to perform authenticated operations (e.g., account creation, signup flows)
- Users don't have accounts yet but need access to organization data (e.g., product catalogs, pricing)
- Operations require organization-level permissions rather than user-level permissions
Use user-scoped auth when:
- User is authenticated and performing actions on their own account
- Accessing user-specific data (account details, usage, billing, payment methods)
- Standard logged-in user workflows (dashboard, settings, account management)
How does token caching work?
Organization tokens are encrypted using AES-256-GCM and stored in Vercel Edge Config, which provides:
- Global edge caching: Tokens are cached at Vercel's edge locations worldwide for sub-millisecond access
- Automatic invalidation: When tokens are updated via the cron job, Edge Config automatically invalidates the cache globally
- High availability: 99.99% uptime SLA with automatic failover
- Encryption at rest: All data in Edge Config is encrypted, with an additional encryption layer from the auth package
The cron job proactively refreshes tokens every 55 minutes (tokens expire after 60 minutes) to ensure cached tokens remain valid. If a cached token fails during a request, the client automatically fetches a fresh one from the Kraken API and updates the cache.
What happens if the cron job fails?
The authentication system has a robust fallback mechanism:
- When a request is made with an expired or invalid cached token, the GraphQL request will fail with an authentication error
- The client automatically detects the authentication failure
- The client requests a fresh token directly from the Kraken API using the organization secret key
- The new token is encrypted and cached in Edge Config for future requests
- The original GraphQL request is retried with the fresh token
While this fallback works seamlessly from the user's perspective, it adds latency (additional API round-trip). Monitor your cron job's success rate in Vercel to ensure tokens are refreshed proactively.
Can I use organization-scoped auth in client components?
No. Organization-scoped auth is strictly server-side only. Exposing the organization token to client-side code would be a critical security vulnerability, granting malicious actors organization-level permissions.
Use it only in:
- Server-Side Rendering (
getServerSideProps,getStaticPropsin Pages Router) - Server Components (App Router)
- API Route Handlers (
pages/api/...in Pages Router,app/.../route.tsin App Router) - Server Actions (App Router)
Next steps
Now that you have organization-scoped authentication configured, explore these related guides:
- Masquerade Auth: Enable staff impersonation for customer support
- Anonymous Auth: Pre-signed key authentication for temporary access
- Kraken OAuth: Enable OAuth-based authentication flows
- Building Forms: Production-ready forms with validation and error handling
API Reference
Quick reference to relevant functions:
createAuthConfig: Configure organization-scoped auth with Edge Config and encryptiongetOrganizationScopedGraphQLClient: Make organization-scoped GraphQL requestscreateUpdateOrgTokenHandler: Cron job handler for automatic token refreshgetSession: Check user authentication status to choose appropriate auth scope