Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .syncpackrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
"dependencyTypes": ["peer"],
"packages": ["@imtbl/auth-next-server", "@imtbl/auth-next-client", "@imtbl/sdk"],
"isIgnored": true
},
{
"label": "Allow Next.js 14 in auth-next devDependencies for compatibility",
"dependencies": ["next"],
"dependencyTypes": ["dev"],
"packages": ["@imtbl/auth-next-server", "@imtbl/auth-next-client"],
"isIgnored": true
}
]
}
5 changes: 5 additions & 0 deletions examples/passport/auth-next-default-test/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Required for NextAuth.js
AUTH_SECRET=your-secret-key-min-32-characters-long-change-this-in-production

# Optional: Override default clientId (will use sandbox/production defaults if not set)
# NEXT_PUBLIC_IMMUTABLE_CLIENT_ID=your-client-id-here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does setting this actually override the default client id?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for testing purpose or demoing usage? for later, I think this won't be an usecase.

4 changes: 4 additions & 0 deletions examples/passport/auth-next-default-test/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"root": true,
"extends": ["next/core-web-vitals", "next"]
}
32 changes: 32 additions & 0 deletions examples/passport/auth-next-default-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Default Auth Test App

Test application for `@imtbl/auth-next-client` and `@imtbl/auth-next-server` with default auth configuration.

## Setup

```bash
# Install dependencies
pnpm install

# Create .env.local
cp .env.example .env.local

# Run dev server
pnpm dev
```

## Testing

This app tests:
- ✅ Zero-config setup (no clientId or redirectUri required)
- ✅ Auto-detection of clientId (sandbox vs production)
- ✅ Auto-derivation of redirectUri
- ✅ useLogin hook with optional config
- ✅ useLogout hook with optional config
- ✅ Custom config overrides

## URLs

- Home: http://localhost:3000
- Callback: http://localhost:3000/callback
- Auth API: http://localhost:3000/api/auth
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { handlers } from "@/lib/auth";

export const { GET, POST } = handlers;
26 changes: 26 additions & 0 deletions examples/passport/auth-next-default-test/app/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { CallbackPage } from "@imtbl/auth-next-client";
import {
DEFAULT_PRODUCTION_CLIENT_ID,
DEFAULT_SANDBOX_CLIENT_ID,
} from "@imtbl/auth-next-client";

export default function Callback() {
// Auto-detect environment and derive config
const isSandbox = typeof window !== 'undefined' &&
(window.location.hostname.includes('sandbox') || window.location.hostname.includes('localhost'));

const config = {
clientId: isSandbox ? DEFAULT_SANDBOX_CLIENT_ID : DEFAULT_PRODUCTION_CLIENT_ID,
redirectUri: typeof window !== 'undefined' ? `${window.location.origin}/callback` : '/callback',
};

return (
<div>
<h1>Processing authentication...</h1>
<CallbackPage config={config} redirectTo="/" />
</div>
);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { useEffect, useState } from "react";
import {
DEFAULT_PRODUCTION_CLIENT_ID,
DEFAULT_SANDBOX_CLIENT_ID,
} from "@imtbl/auth-next-client";

export function ConfigInfo() {
const [info, setInfo] = useState<{
hostname: string;
isSandbox: boolean;
expectedClientId: string;
redirectUri: string;
} | null>(null);

useEffect(() => {
const hostname = window.location.hostname;
const isSandbox = hostname.includes('sandbox') || hostname.includes('localhost');
const expectedClientId = isSandbox ? DEFAULT_SANDBOX_CLIENT_ID : DEFAULT_PRODUCTION_CLIENT_ID;
const redirectUri = `${window.location.origin}/callback`;

setInfo({
hostname,
isSandbox,
expectedClientId,
redirectUri,
});
}, []);

if (!info) {
return <div>Loading config info...</div>;
}

return (
<div className="info">
<h3>📋 Auto-Detected Configuration</h3>
<ul>
<li><strong>Hostname:</strong> <span className="code">{info.hostname}</span></li>
<li><strong>Environment:</strong> <span className="code">{info.isSandbox ? 'Sandbox' : 'Production'}</span></li>
<li><strong>ClientId:</strong> <span className="code">{info.expectedClientId}</span></li>
<li><strong>RedirectUri:</strong> <span className="code">{info.redirectUri}</span></li>
</ul>
<p><small>These values are automatically detected and don&apos;t need to be configured!</small></p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { useLogin, useImmutableSession } from "@imtbl/auth-next-client";

export function LoginButton() {
const { isAuthenticated, session } = useImmutableSession();
const { loginWithPopup, isLoggingIn, error } = useLogin();

if (isAuthenticated && session) {
return (
<div className="success">
<h3>✅ Logged In</h3>
<p>Email: <span className="code">{session.user?.email || 'N/A'}</span></p>
<p>User ID: <span className="code">{session.user?.sub || 'N/A'}</span></p>
</div>
);
}

return (
<div>
<h3>Test Login (Zero Config)</h3>
<button
onClick={() => loginWithPopup()}
disabled={isLoggingIn}
>
{isLoggingIn ? "Signing in..." : "🔐 Sign In with Popup"}
</button>
{error && <p className="error">Error: {error}</p>}
<p className="info">
This uses <span className="code">loginWithPopup()</span> with no config!<br/>
ClientId and redirectUri are auto-detected.
</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { useLogin, useImmutableSession } from "@imtbl/auth-next-client";
import { useState } from "react";

export function LoginWithOverride() {
const { isAuthenticated, session } = useImmutableSession();
const { loginWithPopup, isLoggingIn, error } = useLogin();
const [customClientId, setCustomClientId] = useState("");

if (isAuthenticated && session) {
return (
<div className="success">
<h3>✅ Logged In with Override</h3>
<p>Email: <span className="code">{session.user?.email || 'N/A'}</span></p>
</div>
);
}

return (
<div>
<h3>Test Login with Custom ClientId Override</h3>
<div style={{ marginBottom: '1rem' }}>
<input
type="text"
placeholder="Enter custom clientId (optional)"
value={customClientId}
onChange={(e) => setCustomClientId(e.target.value)}
style={{
width: '100%',
padding: '0.5rem',
fontSize: '0.9rem',
borderRadius: '0.25rem',
border: '1px solid #ccc',
}}
/>
<small style={{ display: 'block', marginTop: '0.5rem', color: '#666' }}>
Leave empty to use default sandbox clientId
</small>
</div>
<button
onClick={() => loginWithPopup(customClientId ? {
clientId: customClientId,
// Other fields will use defaults
} : undefined)}
disabled={isLoggingIn}
>
{isLoggingIn ? "Signing in..." : "🔐 Sign In with Override"}
</button>
{error && <p className="error">Error: {error}</p>}
<p className="info">
This tests <span className="code">loginWithPopup(&#123; clientId: &apos;...&apos; &#125;)</span><br/>
Custom clientId overrides the default, but redirectUri is still auto-detected.
</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";

import { useLogout, useImmutableSession } from "@imtbl/auth-next-client";

export function LogoutButton() {
const { isAuthenticated } = useImmutableSession();
const { logout, isLoggingOut, error } = useLogout();

if (!isAuthenticated) {
return null;
}

return (
<div>
<h3>Test Logout (Zero Config)</h3>
<button
onClick={() => logout()}
disabled={isLoggingOut}
>
{isLoggingOut ? "Signing out..." : "🚪 Sign Out"}
</button>
{error && <p className="error">Error: {error}</p>}
<p className="info">
This uses <span className="code">logout()</span> with no config!<br/>
Performs federated logout to clear both local and upstream sessions.
</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use client";

import { useLogout, useImmutableSession } from "@imtbl/auth-next-client";
import { useState } from "react";

export function LogoutWithOverride() {
const { isAuthenticated } = useImmutableSession();
const { logout, isLoggingOut, error } = useLogout();
const [customRedirectUri, setCustomRedirectUri] = useState("");

if (!isAuthenticated) {
return null;
}

return (
<div>
<h3>Test Logout with Custom Redirect Override</h3>
<div style={{ marginBottom: '1rem' }}>
<input
type="text"
placeholder="Enter custom logoutRedirectUri (optional)"
value={customRedirectUri}
onChange={(e) => setCustomRedirectUri(e.target.value)}
style={{
width: '100%',
padding: '0.5rem',
fontSize: '0.9rem',
borderRadius: '0.25rem',
border: '1px solid #ccc',
}}
/>
<small style={{ display: 'block', marginTop: '0.5rem', color: '#666' }}>
Leave empty to use default (http://localhost:3000)
</small>
</div>
<button
onClick={() => logout(customRedirectUri ? {
logoutRedirectUri: customRedirectUri,
// ClientId will use default
} : undefined)}
disabled={isLoggingOut}
>
{isLoggingOut ? "Signing out..." : "🚪 Sign Out with Override"}
</button>
{error && <p className="error">Error: {error}</p>}
<p className="info">
This tests <span className="code">logout(&#123; logoutRedirectUri: &apos;...&apos; &#125;)</span><br/>
Custom redirect after logout, but clientId is still auto-detected.
</p>
</div>
);
}
63 changes: 63 additions & 0 deletions examples/passport/auth-next-default-test/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
body {
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 2rem;
max-width: 800px;
margin: 0 auto;
}

h1 {
color: #333;
}

button {
background: #0070f3;
color: white;
border: none;
padding: 0.75rem 1.5rem;
font-size: 1rem;
border-radius: 0.5rem;
cursor: pointer;
margin: 0.5rem 0.5rem 0.5rem 0;
}

button:hover {
background: #0051cc;
}

button:disabled {
background: #ccc;
cursor: not-allowed;
}

.error {
color: #ff0000;
margin-top: 1rem;
}

.success {
color: #00aa00;
margin-top: 1rem;
}

.info {
background: #f0f0f0;
padding: 1rem;
border-radius: 0.5rem;
margin: 1rem 0;
}

.code {
background: #f5f5f5;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.9rem;
}

pre {
background: #f5f5f5;
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
}
Loading