Quick Navigation
Installation
npm install @rownd/next
# or
yarn add @rownd/next
Basic Setup
1. Provider Setup
In your root layout.tsx
:
import { RowndProvider } from '@rownd/next';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<RowndProvider
appKey="<your app key>"
apiUrl="<optional: your api url>" // Enterprise only
hubUrlOverride="<optional: your hub url>" // Enterprise only
postLoginRedirect="/dashboard" // Optional
postLogoutRedirect="/" // Optional
>
{children}
</RowndProvider>
</body>
</html>
);
}
2. Middleware Setup
In your middleware.ts
:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { withRowndMiddleware } from '@rownd/next/server';
export const middleware = withRowndMiddleware((request: NextRequest) => {
return NextResponse.next();
});
export const config = {
matcher: [
// Required for Rownd token handling
'/api/rownd-token-callback',
// Add your protected routes
'/protected/:path*',
'/api/protected/:path*'
]
};
Server-Side Features
The @rownd/next/server
package provides server-side authentication utilities:
import {
withRowndRequireSignIn,
getRowndUser,
getAccessToken,
isAuthenticated,
getRowndUserId,
} from '@rownd/next/server';
import { cookies } from 'next/headers';
Server-Side Functions
Function | Description | Parameters | Return Type | Usage Example |
---|
getRowndUser | Retrieves the current authenticated user’s data from Rownd | cookies: ReadonlyRequestCookies | Promise<RowndServerUser | null> | const user = await getRowndUser(cookies) |
getRowndUserId | Gets the current user’s ID from server context | cookies: ReadonlyRequestCookies | Promise<string | null> | const userId = await getRowndUserId(cookies) |
getRowndAccessToken | Retrieves the current access token from server context | cookies: ReadonlyRequestCookies | Promise<string | null> | const token = await getRowndAccessToken(cookies) |
isAuthenticated | Checks if there is an authenticated user in server context | cookies: ReadonlyRequestCookies | Promise<boolean> | const isAuth = await isAuthenticated(cookies) |
withRowndMiddleware | Higher-order function for Next.js middleware to handle authentication | middleware: NextMiddleware | NextMiddleware | See middleware example below |
withRowndRequireSignIn | Higher-order function to protect routes/pages/API handlers | handler: Function, cookies: ReadonlyRequestCookies | Function | See protected routes example below |
Example Usage
// Middleware setup
export const config = {
matcher: [
'/api/rownd-token-callback', // Required for token handling
'/protected/:path*' // Your protected routes
]
};
// Protected API route
async function handler(req: Request) {
const userId = await getRowndUserId(cookies);
const token = await getRowndAccessToken(cookies);
const user = await getRowndUser(cookies);
if (!userId) {
return Response.json({ error: 'Not authenticated' }, { status: 401 });
}
return Response.json({ userId, user });
}
export const GET = withRowndRequireSignIn(handler, cookies);
Client-Side Features
The client-side features mirror the React SDK functionality but must be used within client components:
The useRownd Hook
The useRownd
hook provides comprehensive authentication and user management functionality:
State Properties
Property | Type | Description | Example Usage | | | | | |
---|
is_initializing | boolean | Whether Rownd is still loading | if (is_initializing) return <Loading /> | | | | | |
is_authenticated | boolean | Whether user is currently signed in | if (is_authenticated) showDashboard() | | | | | |
access_token | string | null | Current JWT access token | headers: { Authorization: Bearer ${access_token} } | | | | | |
user.data | Record<string, any> | User’s profile data | const { first_name, email } = user.data | | user.groups | string[] | Groups the user belongs to | if (user.groups.includes('admin')) |
Authentication Methods
Method | Description | Parameters | Return Type |
---|
requestSignIn() | Triggers sign-in flow | { auto_sign_in?: boolean, identifier?: string, method?: string, post_login_redirect?: string } | void |
signOut() | Signs out current user | None | void |
getAccessToken() | Gets current access token | { waitForToken?: boolean, timeoutMs?: number } | Promise<string> |
User Data Methods
Method | Description | Parameters | Return Type |
---|
setUser() | Updates multiple user fields | Record<string, any> | Promise<void> |
setUserValue() | Updates a single user field | (field: string, value: any) | Promise<void> |
manageAccount() | Opens account management UI | None | void |
Additional Features
Feature | Description | Parameters | Return Type |
---|
getFirebaseIdToken() | Gets Firebase ID token | None | Promise<string> |
getAppConfig() | Gets app configuration | None | Promise<AppConfig> |
passkeys | Passkey authentication methods | { register: () => Promise<void>, authenticate: () => Promise<void> } | Object |
events | Event system for state changes | addEventListener(event: string, callback: Function) | EventEmitter |
Example Usage
'use client';
function ProfileManager() {
const {
is_authenticated,
is_initializing,
user,
setUser,
manageAccount,
getAccessToken
} = useRownd();
if (is_initializing) return <div>Loading...</div>;
if (!is_authenticated) return <div>Please sign in</div>;
const updateProfile = async () => {
await setUser({
first_name: 'John',
last_name: 'Doe'
});
};
const fetchProtectedData = async () => {
const token = await getAccessToken({ waitForToken: true });
// Use token for API calls
};
return (
<div>
<h1>Welcome {user.data.first_name}!</h1>
<button onClick={updateProfile}>Update Profile</button>
<button onClick={manageAccount}>Manage Account</button>
</div>
);
}
Authentication Flow
Combined Server/Client Authentication
// app/profile/page.tsx
import { getRowndUser } from '@rownd/next/server';
import { cookies } from 'next/headers';
import { ClientProfile } from './client-profile';
// Server Component
export default async function ProfilePage() {
const serverUser = await getRowndUser(cookies);
if (!serverUser) {
return <div>Please sign in</div>;
}
// Initial data from server
return (
<div>
<h1>Server-rendered Profile</h1>
<pre>{JSON.stringify(serverUser.data, null, 2)}</pre>
{/* Client component for real-time updates */}
<ClientProfile initialData={serverUser} />
</div>
);
}
// client-profile.tsx
'use client';
export function ClientProfile({ initialData }) {
const { user, is_initializing } = useRownd();
if (is_initializing) {
return <div>Syncing...</div>;
}
return (
<div>
<h2>Real-time Profile Updates</h2>
<pre>{JSON.stringify(user.data, null, 2)}</pre>
</div>
);
}
Protected Routes
Server-Side Protection
// middleware.ts
import { withRowndMiddleware } from '@rownd/next/server';
export const middleware = withRowndMiddleware((request: NextRequest) => {
// Protect specific paths
if (request.nextUrl.pathname.startsWith('/protected')) {
// Additional custom logic
}
return NextResponse.next();
});
Component-Level Protection
// Protected Server Component
import { withRowndRequireSignIn } from '@rownd/next/server';
import { cookies } from 'next/headers';
async function ProtectedPage() {
const user = await getRowndUser(cookies);
return <div>Protected Content for {user.data.email}</div>;
}
export default withRowndRequireSignIn(ProtectedPage, cookies);
// Protected Client Component
'use client';
function ProtectedClientComponent() {
const { is_authenticated, is_initializing } = useRownd();
if (is_initializing) return <div>Loading...</div>;
if (!is_authenticated) return <div>Please sign in</div>;
return <div>Protected Client Content</div>;
}
Data Access
Server-Side Data Access
async function ServerComponent() {
const user = await getRowndUser(cookies);
const token = await getAccessToken(cookies);
// Make authenticated API calls
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${token}`
}
});
return <div>User: {user.data.email}</div>;
}
Client-Side Data Access
'use client';
function ClientComponent() {
const { user, getAccessToken } = useRownd();
const fetchData = async () => {
const token = await getAccessToken();
// Make authenticated API calls
};
return <div>User: {user.data.email}</div>;
}
Best Practices
-
Server/Client Separation
// Server Component
export default async function Page() {
const serverUser = await getRowndUser(cookies);
return <ClientComponent initialData={serverUser} />;
}
// Client Component
'use client';
export function ClientComponent({ initialData }) {
const { user } = useRownd();
// Use initialData for SSR, user for updates
}
-
Efficient Token Handling
// Server-side
const token = await getAccessToken(cookies);
// Token includes app_id and user_id
// Client-side
const { getAccessToken } = useRownd();
const token = await getAccessToken();
-
Error Boundaries
'use client';
class RowndErrorBoundary extends React.Component {
// Implementation
}
Type Definitions
Core Types
Type | Description | Interface |
---|
RowndServerUser | Server-side user type | { data: Record<string, any>; access_token: string; app_id: string; user_id: string; } |
RowndUser | Client-side user type | { data: Record<string, any>; groups: string[]; redacted_fields: string[]; verified_data: Record<string, any>; meta: UserMeta; } |
UserMeta | User metadata | { created_at: string; updated_at: string; last_sign_in: string; } |
SignInOptions | Sign-in configuration | { auto_sign_in?: boolean; identifier?: string; method?: AuthMethod; post_login_redirect?: string; } |
TokenOptions | Token retrieval options | { waitForToken?: boolean; timeoutMs?: number; } |
AuthMethod | Authentication methods | 'email' | 'phone' | 'google' | 'apple' | 'passkey' | 'anonymous' |
Server-Side Types
// Server-side interfaces
interface RowndServerUser {
data: Record<string, any>;
access_token: string;
app_id: string;
user_id: string;
}
interface RowndServerConfig {
app_key: string;
api_url?: string;
hub_url_override?: string;
}
type RowndMiddlewareConfig = {
matcher: string[];
};
// Server utility types
type GetUserFn = (cookies: ReadonlyRequestCookies) => Promise<RowndServerUser | null>;
type GetTokenFn = (cookies: ReadonlyRequestCookies) => Promise<string | null>;
type IsAuthenticatedFn = (cookies: ReadonlyRequestCookies) => Promise<boolean>;
Client-Side Types
// Client-side interfaces
interface RowndUser {
data: Record<string, any>;
groups: string[];
redacted_fields: string[];
verified_data: Record<string, any>;
meta: {
created_at: string;
updated_at: string;
last_sign_in: string;
};
is_loading: boolean;
}
interface UseRowndReturn {
is_initializing: boolean;
is_authenticated: boolean;
user: RowndUser;
requestSignIn: (options?: SignInOptions) => void;
signOut: () => void;
getAccessToken: (options?: TokenOptions) => Promise<string>;
setUser: (data: Record<string, any>) => Promise<void>;
setUserValue: <T>(field: string, value: T) => Promise<void>;
manageAccount: () => void;
getFirebaseIdToken: () => Promise<string>;
getAppConfig: () => Promise<AppConfig>;
passkeys: PasskeyMethods;
events: EventEmitter;
}
TypeScript Examples
Server-Side Example
// app/api/user/route.ts
import {
getRowndUser,
getRowndAccessToken,
withRowndRequireSignIn
} from '@rownd/next/server';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
interface UserPreferences {
theme: 'light' | 'dark';
notifications: boolean;
}
async function handler(req: Request) {
try {
const user = await getRowndUser(cookies);
const token = await getRowndAccessToken(cookies);
if (!user) {
return NextResponse.json(
{ error: 'Not authenticated' },
{ status: 401 }
);
}
// Type-safe user data access
const preferences: UserPreferences = {
theme: user.data.theme || 'light',
notifications: user.data.notifications ?? true
};
return NextResponse.json({
user_id: user.user_id,
preferences,
token
});
} catch (error) {
return NextResponse.json(
{ error: 'Server error' },
{ status: 500 }
);
}
}
export const GET = withRowndRequireSignIn(handler, cookies);
Client-Side Example
// components/user-profile.tsx
'use client';
import { useRownd } from '@rownd/next';
import { useState } from 'react';
interface ProfileFormData {
first_name: string;
last_name: string;
email: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
}
interface ProfileProps {
initialData?: Partial<ProfileFormData>;
}
export function UserProfile({ initialData }: ProfileProps) {
const {
user,
is_authenticated,
is_initializing,
setUser,
getAccessToken
} = useRownd();
const [formData, setFormData] = useState<ProfileFormData>({
first_name: initialData?.first_name || '',
last_name: initialData?.last_name || '',
email: initialData?.email || '',
preferences: initialData?.preferences || {
theme: 'light',
notifications: true
}
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await setUser(formData);
const token = await getAccessToken({ waitForToken: true });
// Make authenticated API call
await fetch('/api/user/preferences', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(formData.preferences)
});
} catch (error) {
console.error('Failed to update profile:', error);
}
};
if (is_initializing) {
return <div>Loading profile...</div>;
}
if (!is_authenticated) {
return <div>Please sign in to view your profile</div>;
}
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="first_name">First Name:</label>
<input
id="first_name"
type="text"
value={formData.first_name}
onChange={e => setFormData(prev => ({
...prev,
first_name: e.target.value
}))}
/>
</div>
<div>
<label htmlFor="email">Email:</label>
<input
id="email"
type="email"
value={formData.email}
onChange={e => setFormData(prev => ({
...prev,
email: e.target.value
}))}
/>
{user.verified_data.email === formData.email && (
<span>✓ Verified</span>
)}
</div>
<div>
<label>
<input
type="checkbox"
checked={formData.preferences.notifications}
onChange={e => setFormData(prev => ({
...prev,
preferences: {
...prev.preferences,
notifications: e.target.checked
}
}))}
/>
Enable Notifications
</label>
</div>
<button type="submit">Save Changes</button>
</form>
);
}
Combined Server/Client Example
// app/profile/page.tsx
import { getRowndUser } from '@rownd/next/server';
import { cookies } from 'next/headers';
import { UserProfile } from '@/components/user-profile';
interface PageProps {
params: Record<string, string>;
searchParams: Record<string, string>;
}
export default async function ProfilePage({ params, searchParams }: PageProps) {
const user = await getRowndUser(cookies);
if (!user) {
return <div>Please sign in to view your profile</div>;
}
// Pass server-fetched data to client component
return (
<div>
<h1>User Profile</h1>
<UserProfile
initialData={{
first_name: user.data.first_name,
last_name: user.data.last_name,
email: user.data.email,
preferences: user.data.preferences
}}
/>
</div>
);
}
Examples
Full Authentication Flow
// app/auth/page.tsx
import { getRowndUser } from '@rownd/next/server';
import { cookies } from 'next/headers';
import { AuthClient } from './auth-client';
export default async function AuthPage() {
const serverUser = await getRowndUser(cookies);
return <AuthClient initialUser={serverUser} />;
}
// auth-client.tsx
'use client';
export function AuthClient({ initialUser }) {
const {
is_initializing,
is_authenticated,
user,
requestSignIn,
signOut
} = useRownd();
if (is_initializing) {
return <div>Loading...</div>;
}
return (
<div>
{is_authenticated ? (
<div>
<h1>Welcome {user.data.first_name}!</h1>
<button onClick={signOut}>Sign Out</button>
</div>
) : (
<button onClick={() => requestSignIn()}>Sign In</button>
)}
</div>
);
}
For more examples and detailed client-side features, refer to the React SDK documentation.