Update the auth integration
Re-validate an existing auth integration against the current Enjab Auth contract.
When Enjab Auth changes, the Enjab Auth changelog lists exactly what changed and the action to adopt it. Apply the entries your tool has not adopted yet, then verify against the Sign in with Enjab Auth reference. This page covers the points most worth re-checking.
The update prompt
Hand an agent this message:
Update this project's "Sign in with Enjab" integration to the current Enjab Auth contract.
Read https://developers.enjab.ae/llms.txt and the Enjab Auth changelog at
https://developers.enjab.ae/docs/changelog/enjab-auth, then apply ONLY the changelog items
this project has not adopted yet. These are integration changes (the EnjabUser type, the
/api/auth/callback and /api/auth/logout routes, the proxy/middleware, and how getUser()
calls /userinfo), not component installs, so edit the affected files to match the reference
in the guide. Keep getUser() calling /userinfo fresh with `cache: "no-store"` and never
cache the user. Do not refactor or restyle anything else, keep the change surface minimal.The agent will fetch the docs and the changelog, check your integration against the latest entries, and apply only what you have not already adopted.
Latest change
The is_active field was removed from the user object (Enjab Auth blocks disabled accounts at the door, so a tool only ever sees active users). Delete is_active from your EnjabUser type and any code that read it. See the changelog for the full history.
Re-validate every request
This is critical and often the first thing to get wrong when updating.
Cache invalidation leak
On each request, call getUser() fresh. This must call /userinfo with cache: "no-store". Never cache the user object beyond the request, or trust your cookie's claims.
A signed-out user, a disabled user, or a user whose role changed at Enjab Auth will keep working in your tool if you cache them. Enjab Auth cannot touch your cookie. The only way central sign-out, account disable, and role changes reach you is if you call /userinfo every request.
Always use the reference implementation exactly as written.
export const getUser = cache(async (): Promise<EnjabUser | null> => {
const token = (await cookies()).get(SESSION_COOKIE)?.value;
if (!token) return null;
const res = await fetch(ISSUER + "/api/oauth/userinfo", {
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
});
return res.ok ? ((await res.json()) as EnjabUser) : null;
});Call getUser() on every page or endpoint that needs the user, and check the result:
import { redirect } from "next/navigation";
import { getUser, loginUrl } from "@/lib/enjab-auth";
export default async function Page() {
const user = await getUser();
if (!user) redirect(loginUrl("/dashboard"));
return <p>Hello {user.name}</p>;
}No login UI of your own
Your tool must not have a login button or login page. The moment a signed-out user touches any page, redirect straight to Enjab Auth's /authorize endpoint. Enjab Auth handles passwords, two-factor, and the greeting screen.
Use the middleware proxy from the reference to redirect instantly:
export function proxy(req: NextRequest) {
const { pathname } = req.nextUrl;
if (pathname.startsWith("/api/auth")) return NextResponse.next(); // callback/logout are public
if (req.cookies.has("enjab_session")) return NextResponse.next();
const u = new URL(ISSUER + "/authorize");
u.searchParams.set("client_id", process.env.ENJAB_CLIENT_ID!);
u.searchParams.set("redirect_uri", process.env.ENJAB_REDIRECT_URI!);
u.searchParams.set("response_type", "code");
u.searchParams.set("state", pathname);
return NextResponse.redirect(u);
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)"],
};Sign-out flow
Your tool does not clear the session or call any sign-out method. A "Sign out" button navigates to your /api/auth/logout route. Enjab Auth shows a confirmation page first, so an accidental click never logs anyone out. On confirm, Enjab Auth sends the user back to your logout route with ?confirmed=1, and only then do you clear your tool's session.
export async function GET(req: NextRequest) {
if (req.nextUrl.searchParams.get("confirmed") === "1") {
const res = NextResponse.redirect(`${ISSUER}/logged-out?client_id=${process.env.ENJAB_CLIENT_ID}`);
res.cookies.delete(SESSION_COOKIE);
return res;
}
return NextResponse.redirect(`${ISSUER}/logout?client_id=${process.env.ENJAB_CLIENT_ID}`);
}Wire your sign-out button to /api/auth/logout:
<Button onClick={() => { window.location.href = "/api/auth/logout"; }}>
Sign out
</Button>Role gating (only if role-aware)
By default, Enjab Auth does not send roles. Access is binary: if Enjab Auth let the user in, they have access. Do not gate by role unless your tool is explicitly marked "role-aware" by an Enjab Auth admin.
If your tool is role-aware, you will receive a roles array in the user object. Gate internal features like this:
if (!user.roles?.includes("reception") && !user.is_super_admin) {
return <p>You don't have access to this section.</p>;
}Never invent your own role names or gate on roles if your tool is not role-aware. If roles are absent from the user object, your tool is not role-aware, and you should not branch on them.
Compare against the reference
Your integration should match the reference implementation in the Enjab Auth guide at https://developers.enjab.ae/llms.txt. In particular:
getUser()calls/api/oauth/userinfowithcache: "no-store"- Your
/api/auth/callbackroute exchanges the code and stores the token in an httpOnly cookie - Your
/api/auth/logoutroute waits for?confirmed=1before clearing the session - Your middleware redirects signed-out users straight to
/authorize, no button - All environment variables are set:
ENJAB_CLIENT_ID,ENJAB_CLIENT_SECRET,ENJAB_REDIRECT_URI,ENJAB_ISSUER
If you are not using Next.js, the same principles apply: always call /userinfo fresh, never cache the user, redirect unsigned users instantly, and confirm sign-out before clearing the session.