@econneq/gql-auth
v2.0.15
Published
GraphQL auth utilities for multi-tenant and single-tenant Next.js projects.
Maintainers
Readme
@econneq/gql-auth
GraphQL + Auth utilities for multi-tenant and single-tenant Next.js apps.
Handles:
- Server-side (internal Docker network) and client-side (browser proxy) GraphQL
- Multi-tenant header injection (
X-Tenant-ID,X-Process-ID,X-Widget-ID) - JWT auth on both server and client
- File uploads (GraphQL multipart spec)
- Batch mutations with per-item error reporting
Installation
npm install @econneq/gql-authPeer dependencies
npm install @apollo/client graphql next reactEnvironment Variables
| Variable | Side | Description |
|---|---|---|
| DOMAIN_URL | server | Base domain, e.g. e-conneq.com |
| PROTOCOL | server | e.g. https:// |
| BACKEND_URL_INT | server | Internal Docker URL, e.g. api:8000 |
| BACKEND_URL_EXT | server | External backend host, e.g. admin.e-conneq.com |
| NEXT_PUBLIC_DOMAIN_URL | both | Same as DOMAIN_URL (client-accessible) |
| NEXT_PUBLIC_PROTOCOL | both | Same as PROTOCOL (client-accessible) |
| NEXT_PUBLIC_BACKEND_URL_EXT | both | Same as BACKEND_URL_EXT (client-accessible) |
| API_KEY | server | Optional service-to-service key |
The internal URL (
BACKEND_URL_INT) is never exposed to the browser. Client-side mutations route through Next.js/api/graphqlproxy.
Config Object
Every function accepts a config object describing your tenants.
// config.ts (shared across your project)
import type { ConfigType } from "@econneq/gql-auth";
export const config: ConfigType = {
Subdomains: [
{ id: 1, subdomain: "acme", processId: "proc-1", widgetID: "wgt-1" },
{ id: 2, subdomain: "globex", processId: "proc-2" },
],
};Single-tenant: put one entry in Subdomains. The library will always use it
without requiring a domain parameter.
Quick start
1. Register the cookie helper (Next.js app)
In your root layout (app/layout.tsx) or instrumentation file:
import { cookies } from "next/headers";
import { registerCookiesFn } from "@econneq/gql-auth";
registerCookiesFn(cookies);This lets the Apollo client forward cookies server-side without a hard
next/headers import inside the package.
2. Server-side query (Server Component / Route Handler)
import { queryServerGraphQL } from "@econneq/gql-auth";
import { config } from "@/config";
import { GET_USERS } from "@/graphql/queries";
// Multi-tenant
const data = await queryServerGraphQL({
query: GET_USERS,
variables: { active: true },
domain: "acme", // ← matches Subdomains[].subdomain
config,
});
// Single-tenant (omit domain)
const data = await queryServerGraphQL({
query: GET_USERS,
config,
});3. Server-side mutation (Server Action)
"use server";
import { mutateServerGraphQL } from "@econneq/gql-auth";
const result = await mutateServerGraphQL({
query: CREATE_USER_MUTATION,
variables: { input: formData },
domain: params.domain,
config,
});4. Client-side mutation with ApiFactory
"use client";
import { ApiFactory, getToken } from "@econneq/gql-auth";
import { useRouter } from "next/navigation";
import { config } from "@/config";
export function MyForm({ params }: { params: { domain: string } }) {
const router = useRouter();
const handleSubmit = async (formData: any) => {
await ApiFactory({
newData: formData,
mutationName: "createUser",
modelName: "user",
successField: "username",
query: CREATE_USER_MUTATION,
params: { domain: params.domain }, // omit for single-tenant
config,
router,
token: getToken() ?? undefined,
onAlert: ({ title, status }) => {
// Connect to your toast/alert system
console.log(title, status);
},
redirect: true,
redirectPath: "/dashboard",
});
};
return <form onSubmit={...}>...</form>;
}Batch mutations
Pass an array to newData:
await ApiFactory({
newData: [item1, item2, item3],
mutationName: "createProduct",
modelName: "product",
// ...
});File uploads
await ApiFactory({
newData: formData,
getFileMap: (item) => ({ avatar: item.avatarFile }),
// ...
});Return data instead of alert
const user = await ApiFactory<{ id: string; username: string }>({
returnResponseObject: true,
// ...
});5. Direct upload mutation
For full control over multipart uploads:
import { uploadGraphQLMutation } from "@econneq/gql-auth";
const response = await uploadGraphQLMutation({
query: UPLOAD_DOCUMENT_MUTATION.loc!.source.body,
variables: { title: "Report", file: null },
fileMap: { file: selectedFile },
params: { domain: "acme" },
config,
token: userToken,
});6. Auth utilities
Client-side
import { getToken, getUser, logout } from "@econneq/gql-auth";
const token = getToken(); // cookie → localStorage
const user = getUser(); // decoded JwtPayload
logout(); // clears cookies + localStorageServer-side
import {
getServerToken,
getServerUser,
getPermissions,
} from "@econneq/gql-auth";
const token = await getServerToken();
const user = await getServerUser();
// Does the user have any access to module 42?
const canRead = getPermissions(user?.permissions ?? [], 42);
// Does the user have CREATE access to module 42?
const canCreate = getPermissions(user?.permissions ?? [], 42, "C");7. Apollo client (advanced)
Direct access when you need a custom query/mutation outside ApiFactory:
import { getApolloClient } from "@econneq/gql-auth";
const client = await getApolloClient({
domain: "acme", // omit for single-tenant
config,
});
const { data } = await client.query({ query: MY_QUERY });Architecture
Browser Next.js Server Backend (Docker)
───────── ────────────── ────────────────
uploadGraphQLMutation ──► /api/graphql (proxy) ──► api:8000/graphql/
ApiFactory ──► /api/graphql (proxy) ──► api:8000/graphql/
queryServerGraphQL ────────────────────────────► http://api:8000/graphql/
mutateServerGraphQL ────────────────────────────► http://api:8000/graphql/
getApolloClient ────────────────────────────► http://api:8000/graphql/Server-side calls skip the proxy entirely and hit the backend directly through Docker's internal network — faster and no public exposure.
Permissions format
Permission strings encode resource + action:
"42" → access resource 42 (any action)
"42C" → create on resource 42
"42R" → read on resource 42
"42U" → update on resource 42
"42D" → delete on resource 42Changelog
2.0.0
- Centralised env resolution (
env.ts) - Tenant resolution separated into
tenant.ts— single-tenant fallback mutateServerGraphQLadded (server-side mutations)ApiFactorytyped generics, batch partial-error reportingregisterCookiesFnremoves hardnext/headersdependencygetServerCookieStringhelper exportedparseGraphQLErrorsexported for custom use- Subdomains[0] fallback for single-tenant setups
uploadGraphQLMutationno longer throws on missing tenant in single-tenant mode
