npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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-next

Features

  • 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-next

Step 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/callback

Step 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 token
  • tekcify_refresh_token - Refresh token
  • tekcify_expires_at - Expiration timestamp
  • tekcify_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:

  1. Redirect URI matches exactly (including trailing slashes)
  2. Callback route is properly configured
  3. Code verifier is stored and retrieved correctly

Token refresh failing

Check:

  1. Refresh token is still valid (not expired or revoked)
  2. Client credentials are correct
  3. Network connectivity to auth server

License

MIT