@tekcify/auth-next
v2.0.0
Published
Next.js SDK for Tekcify Auth. Provides React components, hooks, and utilities for integrating authentication into Next.js applications with minimal configuration.
Downloads
967
Readme
@tekcify/auth-next
Next.js SDK for Tekcify Auth. Provides React components, hooks, and utilities for integrating authentication into Next.js applications with minimal configuration.
Installation
npm install @tekcify/auth-next
# or
pnpm add @tekcify/auth-next
# or
yarn add @tekcify/auth-nextFeatures
- ✅ React Hooks -
useAuth(),useSession(),useUser()for easy state management - ✅ Pre-built Components -
<LoginButton />and<LogoutButton />ready to use - ✅ Automatic Token Refresh - Handles token expiration automatically
- ✅ Secure Token Storage - Uses HTTP-only cookies (server-side) or secure cookies (client-side)
- ✅ TypeScript Support - Full type definitions included
- ✅ Next.js App Router & Pages Router - Works with both routing systems
- ✅ SSR Compatible - Server-side rendering support
Quick Start
Step 1: Install Dependencies
pnpm add @tekcify/auth-nextStep 2: Configure Environment Variables
Create a .env.local file in your Next.js project root:
NEXT_PUBLIC_CLIENT_ID=your-client-id
NEXT_PUBLIC_CLIENT_SECRET=your-client-secret
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3002/auth/callbackStep 3: Wrap Your App with Provider
App Router (app/layout.tsx)
import { TekcifyAuthProvider } from '@tekcify/auth-next';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<TekcifyAuthProvider
config={{
clientId: process.env.NEXT_PUBLIC_CLIENT_ID!,
clientSecret: process.env.NEXT_PUBLIC_CLIENT_SECRET!,
redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI!,
scopes: ['read:profile', 'write:profile'],
}}
>
{children}
</TekcifyAuthProvider>
</body>
</html>
);
}Pages Router (_app.tsx)
import type { AppProps } from 'next/app';
import { TekcifyAuthProvider } from '@tekcify/auth-next';
export default function App({ Component, pageProps }: AppProps) {
return (
<TekcifyAuthProvider
config={{
clientId: process.env.NEXT_PUBLIC_CLIENT_ID!,
clientSecret: process.env.NEXT_PUBLIC_CLIENT_SECRET!,
redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI!,
scopes: ['read:profile', 'write:profile'],
}}
>
<Component {...pageProps} />
</TekcifyAuthProvider>
);
}Step 4: Create Callback Route
App Router (app/auth/callback/page.tsx)
import { handleCallbackServer } from '@tekcify/auth-next';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export default async function CallbackPage({
searchParams,
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
const cookieStore = await cookies();
const params = new URLSearchParams(
searchParams as Record<string, string>
);
const result = await handleCallbackServer(
{
clientId: process.env.NEXT_PUBLIC_CLIENT_ID!,
clientSecret: process.env.NEXT_PUBLIC_CLIENT_SECRET!,
redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI!,
scopes: ['read:profile', 'write:profile'],
},
params,
() => cookieStore.get('tekcify_code_verifier')?.value || null,
(tokens) => {
const expiresAt = Date.now() + tokens.expiresIn * 1000;
cookieStore.set('tekcify_access_token', tokens.accessToken, {
expires: new Date(expiresAt),
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
});
cookieStore.set('tekcify_refresh_token', tokens.refreshToken, {
expires: 30,
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
});
cookieStore.set('tekcify_expires_at', expiresAt.toString(), {
expires: new Date(expiresAt),
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
});
cookieStore.set('tekcify_scope', tokens.scope, {
expires: new Date(expiresAt),
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
});
},
() => {
cookieStore.delete('tekcify_code_verifier');
}
);
if (result.success) {
redirect('/');
} else {
redirect(`/login?error=${result.error}`);
}
}Pages Router (pages/auth/callback.tsx)
'use client';
import { handleCallback } from '@tekcify/auth-next';
import { useSearchParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function CallbackPage() {
const searchParams = useSearchParams();
const router = useRouter();
useEffect(() => {
const params = new URLSearchParams(searchParams.toString());
handleCallback(
{
clientId: process.env.NEXT_PUBLIC_CLIENT_ID!,
clientSecret: process.env.NEXT_PUBLIC_CLIENT_SECRET!,
redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI!,
scopes: ['read:profile', 'write:profile'],
},
params
).then((result) => {
if (result.success) {
router.push('/');
} else {
router.push(`/login?error=${result.error}`);
}
});
}, [searchParams, router]);
return <div>Processing authentication...</div>;
}Step 5: Use Authentication in Components
'use client';
import { useAuth, LoginButton, LogoutButton } from '@tekcify/auth-next';
export default function HomePage() {
const { session, user, isLoading, login, logout } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!session) {
return (
<div>
<h1>Please log in</h1>
<LoginButton className="px-4 py-2 bg-blue-500 text-white rounded">
Sign In
</LoginButton>
</div>
);
}
return (
<div>
<h1>Welcome, {user?.email}!</h1>
<p>Name: {user?.name || 'N/A'}</p>
<p>Email Verified: {user?.email_verified ? 'Yes' : 'No'}</p>
<LogoutButton className="px-4 py-2 bg-red-500 text-white rounded">
Sign Out
</LogoutButton>
</div>
);
}API Reference
Hooks
useAuth()
Returns the complete authentication context.
const {
session, // Session object with tokens
user, // User information
isLoading, // Loading state
login, // Function to initiate login
logout, // Function to logout
refreshSession // Function to manually refresh tokens
} = useAuth();Example:
function MyComponent() {
const { session, user, login, logout } = useAuth();
if (!session) {
return <button onClick={login}>Login</button>;
}
return (
<div>
<p>Logged in as {user?.email}</p>
<button onClick={logout}>Logout</button>
</div>
);
}useSession()
Returns only the session information.
const { session, isLoading } = useSession();Example:
function ProtectedComponent() {
const { session, isLoading } = useSession();
if (isLoading) return <div>Loading...</div>;
if (!session) return <div>Not authenticated</div>;
return <div>Access token expires at: {new Date(session.expiresAt).toLocaleString()}</div>;
}useUser()
Returns only the user information.
const { user, isLoading } = useUser();Example:
function Profile() {
const { user, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (!user) return <div>Not authenticated</div>;
return (
<div>
<h1>{user.name || user.email}</h1>
<p>Email: {user.email}</p>
<p>Verified: {user.email_verified ? 'Yes' : 'No'}</p>
</div>
);
}Components
<LoginButton />
Pre-built login button component.
Props:
interface LoginButtonProps {
children?: React.ReactNode; // Button content (default: "Login")
className?: string; // CSS classes
disabled?: boolean; // Disable button
}Example:
<LoginButton className="px-4 py-2 bg-blue-500 text-white rounded">
Sign In with Tekcify
</LoginButton><LogoutButton />
Pre-built logout button component.
Props:
interface LogoutButtonProps {
children?: React.ReactNode; // Button content (default: "Logout")
className?: string; // CSS classes
disabled?: boolean; // Disable button
}Example:
<LogoutButton className="px-4 py-2 bg-red-500 text-white rounded">
Sign Out
</LogoutButton>Types
interface AuthConfig {
clientId: string; // OAuth client ID
clientSecret: string; // OAuth client secret
redirectUri: string; // Callback URL
scopes: string[]; // Requested scopes
}
interface Session {
accessToken: string;
refreshToken: string;
expiresAt: number; // Unix timestamp
scope: string;
}Complete Example
Here's a complete example of a protected page:
'use client';
import { useAuth, LoginButton } from '@tekcify/auth-next';
import { useEffect } from 'react';
export default function Dashboard() {
const { session, user, isLoading, login } = useAuth();
useEffect(() => {
if (!isLoading && !session) {
// Optionally redirect to login page
// router.push('/login');
}
}, [session, isLoading]);
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div>Loading...</div>
</div>
);
}
if (!session) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-2xl font-bold mb-4">Authentication Required</h1>
<LoginButton className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Sign In
</LoginButton>
</div>
);
}
return (
<div className="container mx-auto p-8">
<h1 className="text-3xl font-bold mb-4">Dashboard</h1>
<div className="bg-white shadow rounded-lg p-6">
<h2 className="text-xl font-semibold mb-4">User Information</h2>
<p><strong>Email:</strong> {user?.email}</p>
<p><strong>Name:</strong> {user?.name || 'N/A'}</p>
<p><strong>Email Verified:</strong> {user?.email_verified ? 'Yes' : 'No'}</p>
<p><strong>Token Expires:</strong> {new Date(session.expiresAt).toLocaleString()}</p>
</div>
</div>
);
}Server-Side Usage
For server components and API routes, you can access tokens from cookies:
import { cookies } from 'next/headers';
export default async function ServerComponent() {
const cookieStore = await cookies();
const accessToken = cookieStore.get('tekcify_access_token')?.value;
if (!accessToken) {
return <div>Not authenticated</div>;
}
// Use accessToken for API calls
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const data = await response.json();
return <div>{/* Render data */}</div>;
}Token Management
The SDK automatically handles:
- Token Storage: Stores tokens in secure cookies
- Token Refresh: Automatically refreshes expired tokens
- Token Cleanup: Removes tokens on logout
Tokens are stored with the following cookie names:
tekcify_access_token- Access tokentekcify_refresh_token- Refresh tokentekcify_expires_at- Expiration timestamptekcify_scope- Granted scopes
Error Handling
The SDK handles common errors automatically:
- Token Expiration: Automatically refreshes tokens
- Invalid Tokens: Clears tokens and requires re-authentication
- Network Errors: Provides error information through the context
TypeScript Support
Full TypeScript definitions are included. Import types as needed:
import type { AuthConfig, Session, UserInfo } from '@tekcify/auth-next';Troubleshooting
Tokens not persisting
Ensure cookies are set correctly. In production, use HTTPS and set secure: true.
Callback not working
Verify:
- Redirect URI matches exactly (including trailing slashes)
- Callback route is properly configured
- Code verifier is stored and retrieved correctly
Token refresh failing
Check:
- Refresh token is still valid (not expired or revoked)
- Client credentials are correct
- Network connectivity to auth server
License
MIT
