Installs into .claude/skills of the current project.
Are you the author of Iblai Vibe Auth?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/iblai-iblai-vibe-auth)
---
name: iblai-vibe-auth
description: Add ibl.ai SSO authentication to a vanilla Next.js app
globs:
alwaysApply: false
metadata:
kind: ui
---
# /iblai-vibe-auth
Add ibl.ai SSO authentication to a vanilla Next.js app. After completion,
unauthenticated users are redirected to login.iblai.app and returned with
a session -- no API tokens to manage.
> **Templates:** the auth files this skill creates (providers, store, SSO
> callback, `lib/iblai/*`) are bundled here as Jinja2 assets —
> [`assets/`](assets/). You render them into the project in Step 3. See
> [`/iblai-vibe-scaffold`](../../start/iblai-vibe-scaffold/SKILL.md) for the `{{ }}` variable
> contract and the shared patch mechanics.
If `.env.local` already has `NEXT_PUBLIC_MAIN_TENANT_KEY` set to a real
value (not a placeholder like `your-main-platform`, `your-tenant`, `your-platform`,
`your-tenant-key`, `test-tenant`, `main`, or empty), keep that value.
> **Common setup (brand, conventions, env files, verification):** see [docs/skill-setup.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/docs/skill-setup.md).
## Which sign-in architecture? (read first)
Sign-in is the same hosted round trip for every ibl.ai front end
(`login.<domain>/login?app=…&redirect-to=<origin>&tenant=<org>` → back to
`/sso-login-complete?data=…`); what differs is **where the org comes from**.
`/iblai-vibe-start` records the answer in `iblai.env` as `ARCHITECTURE=`;
if it is missing, ask one question before writing files:
| `ARCHITECTURE` | The app is for | Org key | This skill does |
|---|---|---|---|
| `single-org` *(default)* | members of one organization — yours, or the customer you deploy to | pinned: `NEXT_PUBLIC_MAIN_TENANT_KEY` (never `main`) | everything below as written |
| `multi-org` | users who belong to many organizations (the os.ibl.ai model) | from the URL (`/platform/[org]/…`) plus a switcher; `NEXT_PUBLIC_MAIN_TENANT_KEY=main` is the community org | everything below, then **Going multi-org** at the end |
| `headless` | nobody signs in — scripts, CI, a backend | `IBLAI_ORG` per call | not this skill: `/iblai-api-login` and the `iblai-api-*` family |
The parameters, tokens, anonymous access, organization switching, native shells,
and the OS's implementation are in
[docs/auth-model.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/docs/auth-model.md).
## Step 0: Start from vibe-starter? (new projects)
Before running this skill, ask the user:
> Are you starting a new project from scratch? If so, vibe-starter
> (https://github.com/iblai/vibe/tree/main/skills/start/iblai-vibe-ops-init/assets/vibe-starter) is a pre-wired Next.js 16 +
> Tailwind v4 + shadcn/ui template with ibl.ai SSO auth, a responsive navbar,
> and profile/account/notifications pages already in place. Want to use that
> instead?
If yes, copy the bundled starter template from the installed
`iblai-vibe-ops-init` skill's `assets/vibe-starter/` directory (a sibling of
this skill's directory), or fetch it from the vibe repo if those assets are
not installed -- tell the user which path you took:
cp -a <skills-dir>/iblai-vibe-ops-init/assets/vibe-starter/. .
# or, without local assets:
git clone --depth 1 https://github.com/iblai/vibe.git vibe-tmp && cp -a vibe-tmp/skills/start/iblai-vibe-ops-init/assets/vibe-starter/. . && rm -rf vibe-tmp
pnpm install --ignore-scripts
> Run with `--ignore-scripts` to skip package lifecycle (postinstall) scripts.
If they prefer to wire auth into an existing app, continue below.
## Prerequisites
**Want a complete app from scratch?** Use **vibe-starter** (Step 0 above) —
it ships with auth, a navbar, and profile/account/notifications already
wired, so you can skip this skill. For the underlying whole-app scaffold and
its templates, see [`/iblai-vibe-scaffold`](../../start/iblai-vibe-scaffold/SKILL.md).
**This skill** adds auth to a vanilla Next.js app or an existing project by
creating the auth files directly (Step 3).
- Next.js 14+ with App Router (`app/` directory)
- Node.js 18+
## Step 1: Check Environment
Before proceeding, check for a `iblai.env`
in the project root. Look for `PLATFORM`, `DOMAIN`, and `TOKEN` variables.
If the file does not exist or is missing these variables, tell the user:
"You need an `iblai.env` with your platform configuration. Copy the
bundled template and fill in your values:
`cp iblai.env.example iblai.env` (vibe-starter ships the example) — or,
when the project has no `iblai.env.example`:
`curl -o iblai.env https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/iblai.env`"
If `PLATFORM` is set to a real value (not `your-platform` or `your-main-platform`), use it
as the platform key in Step 3. Otherwise use the placeholder and tell the user to replace it.
## Step 2: Customize Auth Interface
STOP here. You MUST ask the user these questions before
proceeding to Step 3. Do NOT skip this step.
### How
Fetch the org's `platform_name` (`GET https://api.{domain}/dm/api/core/orgs/{platform}/metadata/`), ask the user what the app does and whether they want the navbar, generate a headline and tagline, save the `AUTH_*` values to `iblai.env`, STOP for the user to review, then upload the logo (`POST …/platforms/{platform}/public-image-assets/`, three categories) and PUT `auth_web_skillsai` + `auth_web_mentorai` into the org metadata. The exact questions, defaults, upload table, and JSON payload are in [`references/auth-interface.md`](references/auth-interface.md) — follow it verbatim. Never drop the org's other metadata keys (GET, merge, PUT — see `/iblai-vibe-org-metadata`).
## Step 3: Create the auth files
Render the bundled [`assets/`](assets/) Jinja2 templates into the project —
strip the `.j2` suffix and substitute `{{ platform_key }}` with the platform
key. For a `src/` layout, place files under `src/lib`, `src/store`,
`src/providers` accordingly.
| Template (`assets/…`) | Destination |
|---|---|
| `config.ts.j2` | `lib/iblai/config.ts` |
| `auth-utils.ts.j2` | `lib/iblai/auth-utils.ts` |
| `storage-service.ts.j2` | `lib/iblai/storage-service.ts` |
| `tenant.ts.j2` | `lib/iblai/tenant.ts` |
| `iblai-store.ts.j2` | `store/iblai-store.ts` |
| `iblai-providers.tsx.j2` | `providers/iblai-providers.tsx` |
| `iblai-styles.css.j2` | `app/iblai-styles.css` |
| `sso-login-complete-page.tsx.j2` | `app/sso-login-complete/page.tsx` |
Then apply the project patches (each is idempotent — check for a marker
before editing; full mechanics in
[`/iblai-vibe-scaffold` → add-command](../../start/iblai-vibe-scaffold/references/add-command.md)):
- **`next.config`** — add the `@reduxjs/toolkit` webpack `resolve.alias` that
deduplicates RTK. Without it the SDK binds a different `ReactReduxContext`
and RTK Query hooks silently return `undefined`.
- **`globals.css`** — add `@import "./iblai-styles.css";`.
- **`.env.local`** — set `NEXT_PUBLIC_MAIN_TENANT_KEY=<platform key>` (←
`PLATFORM`) and, for server-side platform calls, `IBLAI_API_KEY` (←
`TOKEN`). Nothing else needs deriving — the URL vars default in the
rendered `lib/iblai/config.ts`.
The SSO callback at `app/sso-login-complete/page.tsx` must stay **outside**
the providers (Step 5 / Troubleshooting), or it deadlocks on login.
## Step 4: Install Dependencies and Add Test Script
Add the SDK dependencies and install:
```bash
pnpm add @iblai/iblai-js @reduxjs/toolkit react-redux sonner lucide-react
pnpm install --ignore-scripts
```
> Run with `--ignore-scripts` to skip package lifecycle (postinstall) scripts.
These are:
- `@iblai/iblai-js` -- SDK (auth, data layer, UI components)
- `@reduxjs/toolkit` + `react-redux` -- state management (SDK uses RTK Query)
- `sonner` -- toast notifications
- `lucide-react` -- icons
If `package.json` does not already have a `"test"` script (or it still has the
default `create-next-app` placeholder), add vitest:
```bash
pnpm add -D vitest
```
Then set the test script in `package.json`:
```json
"scripts": {
"test": "vitest run"
}
```
## Step 5: Wire Providers into Layout
Open `app/layout.tsx` and wrap `{children}` with the generated `IblaiProviders`.
Add `viewport-fit=cover` to the metadata so mobile builds (iOS/Android) respect
safe area insets and don't overlap with the status bar.
**If you have no existing providers:**
```tsx
import type { Metadata, Viewport } from "next";
import { IblaiProviders } from "@/providers/iblai-providers";
export const metadata: Metadata = {
title: "My App",
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<IblaiProviders>{children}</IblaiProviders>
</body>
</html>
);
}
```
**If you have existing providers** (e.g., custom contexts):
```tsx
import type { Metadata, Viewport } from "next";
import { IblaiProviders } from "@/providers/iblai-providers";
import { MyProvider } from "./my-provider";
export const metadata: Metadata = {
title: "My App",
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
viewportFit: "cover",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<IblaiProviders>
<MyProvider>
{children}
</MyProvider>
</IblaiProviders>
</body>
</html>
);
}
```
`IblaiProviders` must be the **outermost** provider -- it contains Redux, Auth,
and Organization providers that other components depend on. Place your own providers
inside it.
## Step 6: Configure Environment
Step 3 created `.env.local` with the platform key. Verify it has the
right value (edit the line directly if not):
```bash
NEXT_PUBLIC_MAIN_TENANT_KEY=your-main-platform
```
The API URLs default to hosted `iblai.app` in the rendered
`lib/iblai/config.ts`; override them in `.env.local` only when
self-hosting.
## Step 7: Import SDK Styles
Verify `app/globals.css` has the SDK imports (Step 3 adds these):
```css
@import '@iblai/iblai-js/web-containers/styles';
@source "../node_modules/@iblai/iblai-js/dist/web-containers/source";
```
If these lines are missing, add them near the top of `globals.css`.
## Step 8: Replace Default Home Page
After adding auth, check if `app/page.tsx` still has the default Next.js
content (look for `next/image`, `vercel.svg`, `Vercel`, or
`Get started by editing`). If it does, replace it with the ibl.ai home page —
render [`assets/home-page.tsx.j2`](assets/home-page.tsx.j2) into
`app/(app)/page.tsx` (or `app/page.tsx`), substituting `{{ }}` placeholders.
If the user has already customized their home page, skip this step.
## Step 9: Navbar
If the user said yes to the navbar question in Step 2, run `/iblai-vibe-navbar`
now before starting the dev server.
## Step 10: Start Dev Server
Start the dev server so the user can see the result:
```bash
pnpm dev
```
1. Open http://localhost:3000
2. You should be redirected to https://login.iblai.app
3. Log in (or create a free account)
4. You'll be returned to your app with a fully authenticated session
5. Check browser localStorage -- you should see `axd_token`, `userData`, `tenants`
## What Was Generated
| File | Purpose |
|------|---------|
| `app/sso-login-complete/page.tsx` | SSO callback -- stores tokens from URL into localStorage |
| `lib/iblai/config.ts` | Environment variable accessors (API URLs, platform key, auth URL) |
| `lib/iblai/storage-service.ts` | localStorage wrapper implementing the SDK's StorageService interface |
| `lib/iblai/auth-utils.ts` | `redirectToAuthSpa()`, `hasNonExpiredAuthToken()`, `handleLogout()` |
| `store/iblai-store.ts` | Redux store with `coreApiSlice`, `mentorReducer`, `mentorMiddleware`, and the slice keys the SDK `<Chat>` hard-codes (`chat`, `chatInput`, `chatSliceShared`, `files`, `rbac`, `subscription`, `topBanner`) |
| `providers/iblai-providers.tsx` | Provider chain: ReduxProvider > AuthProvider > TenantProvider |
## What Was Patched
- **`next.config.ts`** -- webpack `resolve.alias` to deduplicate `@reduxjs/toolkit`,
`turbopack: {}` for Next.js 16+. Without the dedup,
SDK components use a different `ReactReduxContext` and RTK Query hooks silently
return `undefined` with zero HTTP requests.
- **`globals.css`** -- SDK base styles import.
- **`.env.local`** -- platform key (`NEXT_PUBLIC_MAIN_TENANT_KEY`) and, for
server-side platform calls, `IBLAI_API_KEY`.
## Advanced: Route Groups
For production apps, consider moving the SSO callback outside the auth
providers using Next.js route groups:
```
app/
├── (auth)/
│ └── sso-login-complete/page.tsx ← Outside providers (no AuthProvider wrapper)
└── (app)/
├── layout.tsx ← IblaiProviders wraps only this group
└── page.tsx
```
This prevents the SSO callback deadlock where `AuthProvider` blocks rendering
before tokens are stored. Step 3 places the callback at
`app/sso-login-complete/` (flat, no route group) which works for simple apps.
For the route group pattern, see the **vibe-starter** reference app:
https://github.com/iblai/vibe/tree/main/skills/start/iblai-vibe-ops-init/assets/vibe-starter
## Troubleshooting
### Sign-in redirects to login.iblai.app and never comes back
Every origin the app runs on — `http://localhost:3000`, the deployed URL, a
Tauri custom scheme — must be in the organization's **allowed redirect
origins**. Nothing in the app can fix this; ask your ibl.ai operator to add
the origin. Also check the org key is not `main` (the shared org) or a
placeholder — the starter renders an alert for those instead of looping.
### "Unknown server error" with custom-domains on localhost
The SDK calls `/api/custom-domains?domain=localhost` as part of platform detection.
This fails on localhost but is **harmless** -- the platform is resolved from
`NEXT_PUBLIC_MAIN_TENANT_KEY` in `.env.local` instead. You can safely ignore
this console error during local development.
### SDK components show undefined / no API requests
`@reduxjs/toolkit` must be deduplicated in `next.config.ts`. Without the
webpack alias, the SDK's components use a different `ReactReduxContext` than
your app's `StoreProvider`, so RTK Query hooks silently return `undefined`.
Verify your next.config has:
```javascript
config.resolve.alias['@reduxjs/toolkit'] = rtkDir;
```
### Auth redirect loops
The SSO callback page (`app/sso-login-complete/page.tsx`) must NOT be wrapped
by `AuthProvider`. If it is, `AuthProvider` detects "no tokens" and redirects
to login before the callback can store the tokens -- creating an infinite loop.
If this happens, use the route group pattern described above to separate the
SSO callback from the authenticated routes.
### Blank screen after login
Check that `.env.local` has `NEXT_PUBLIC_MAIN_TENANT_KEY` set. Without it,
the platform resolution falls back to custom-domain detection which fails on
localhost, leaving the app in a broken state.
## Next Steps
After auth is set up, add more features using skills -- ask your AI assistant to use:
| Skill | What It Adds |
|-------|-------------|
| `/iblai-vibe-agent-chat` | In-process agent chat surface |
| `/iblai-vibe-profile` | User profile dropdown + settings page |
| `/iblai-vibe-account` | Account/organization settings page |
| `/iblai-vibe-analytics` | Analytics dashboard page |
| `/iblai-vibe-notification` | Notification bell + center page |
For a complete reference app with auth, navbar, and pages already wired:
https://github.com/iblai/vibe/tree/main/skills/start/iblai-vibe-ops-init/assets/vibe-starter
**Brand guidelines**: [BRAND.md](https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/BRAND.md)
## Going multi-org (`ARCHITECTURE=multi-org`)
Five moves, all with SDK pieces vibe-starter already imports — the full
version is `providers/index.tsx` in [iblai/os](https://github.com/iblai/os):
1. **Org in the URL** — `app/platform/[tenantKey]/…`; read it with
`useParams()`; keep `resolveAppTenant()` for the default org.
2. **Providers** — `TenantProvider currentTenant={stored} requestedTenant={fromRoute}`
with `saveUserTokens`, `saveUserTenants`, `saveCurrentTenant`, `saveTenant`
implemented (the starter's `iblai-providers.tsx` already has them).
3. **Switching** — `UserProfileDropdown showTenantSwitcher` (or `TenantSwitcher`)
→ `handleTenantSwitch(org, { authUrl })` from `@iblai/iblai-js/web-utils`:
clears storage, re-enters the SPA with `tenant=`, broadcasts to other tabs.
4. **Anonymous per agent** — in `AuthProvider`'s `middleware`, for
`^/platform/([^/]+)/([^/]+)` fetch the agent's public settings
(`useLazyGetMentorPublicSettingsQuery`) and return `false` when
`allow_anonymous` or `mentor_visibility === "viewable_by_anyone"`.
5. **Landing path guard** — after SSO, reset to `/` when the stored path
names an org other than the one just authenticated
(`resolveRedirectPath` on `SsoLogin`).
Register every deployed origin as an allowed redirect origin, exactly as for
a single-org app.