🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

mytpen-auth

Package Overview
Dependencies
Maintainers
1
Versions
65
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mytpen-auth

Better Auth integration package for MYTPEN authentication

latest
Source
npmnpm
Version
1.1.15
Version published
Maintainers
1
Created
Source

MYTPEN Auth Package

A Better Auth integration package for MYTPEN authentication with setup.

🚀 Setup

Get authentication working in your Next.js app with these 7 steps:

Step 1: Install the Package

pnpm install mytpen-auth

Step 2: Create OIDC App and Get Environment Variables

  • Go to https://dashboard.mytpen.app/admin/apps
  • Sign in to MYTPEN account as Admin
  • Create a new OIDC app
  • Copy the environment values

Step 3: Set Up Redis [Use same Redis instance that the dashboard uses]

Use the same Upstash (or Redis) instance as the MYTPEN dashboard when possible so Better Auth secondary storage matches production.

The bundled auth uses @upstash/redis with Redis.fromEnv(). The usual variables are:

# From the Upstash console (REST API) — preferred for this package
UPSTASH_REDIS_REST_URL="https://your-instance.upstash.io"
UPSTASH_REDIS_REST_TOKEN=your-rest-token

Alternative (e.g. Vercel / other hostings): some platforms only inject different names. If UPSTASH_* are not set, compatible values often work under names such as:

# Alternative naming — only if your host does not provide UPSTASH_REDIS_* 
# KV_REST_API_URL=https://your-endpoint.upstash.io
# KV_REST_API_TOKEN=your-rest-api-token
# KV_URL=redis://default:password@host:port
# REDIS_URL=redis://default:password@host:port

See Upstash Redis on Vercel Marketplace or the Upstash console for creating a database and copying the REST URL and token.

Step 4: Create .env File

Create a .env file in your project root and paste the values:

MYTPEN_AUTH_SECRET=wIXyY1-qegt7qN4VOAThlwB7qhOFM7uc  # use any random string as secret-key

# get these from mytpen dashboard
MYTPEN_PROVIDER_ID=your-app-id  
MYTPEN_CLIENT_ID=bJgGbbqKVgqjQXZgOPyWheoMDePFGRUN
MYTPEN_CLIENT_SECRET=MMFZfWLhditpjvQVUAZpRPGLGGBpNQNv

# IdP origin OR full Better Auth base (both work — avoids doubled /api/auth paths).
# Used for server-side revoke URL resolution, and for MytpenAuthProvider billingURL / postSignOutRedirect when you set those from env.
MYTPEN_AUTH_SERVER=https://dashboard.mytpen.app

# Comma-separated origins for Better Auth (e.g. https://app.example.com,http://localhost:3000)
# MYTPEN_TRUSTED_ORIGINS=http://localhost:3000

# Base URL of this app’s auth API (Better Auth callbacks). Prefer BETTER_AUTH_URL; MYTPEN_AUTH_BASEURL is accepted as fallback.
# BETTER_AUTH_URL=http://localhost:3000
MYTPEN_AUTH_BASEURL=http://localhost:3000

# Client: must match MYTPEN_PROVIDER_ID for authClient.getAccessToken / refreshToken / OAuth calls in the browser.
# NEXT_PUBLIC_MYTPEN_PROVIDER_ID=your-app-id

# OpenAPI Scalar reference page (optional): set IS_DEBUG=true to enable default reference UI
# IS_DEBUG=true

# Redis — @upstash/redis via Redis.fromEnv() (secondary storage for Better Auth)
UPSTASH_REDIS_REST_URL="https://your-instance.upstash.io"
UPSTASH_REDIS_REST_TOKEN=

# Alternative: if your platform only exposes Vercel-style names, map or set e.g.:
# KV_REST_API_URL / KV_REST_API_TOKEN (see Step 3)

For the repository demo (demo/), the dev server uses port 3001 — set MYTPEN_AUTH_BASEURL=http://localhost:3001 (and the same for BETTER_AUTH_URL if you use it) so OAuth redirects match the app origin.

Step 5: Create API Route Handler

Create app/api/auth/[...all]/route.ts:

// app/api/auth/[...all]/route.ts
import { auth } from "mytpen-auth";
import { toNextJsHandler } from 'mytpen-auth';

export const { POST, GET } = toNextJsHandler(auth);

Step 6: Add Auth Provider to Your App (server component)

Update app/layout.tsx - must be server component:

// app/layout.tsx
import { MytpenAuthProvider } from "mytpen-auth/provider";
import { LogoSvg } from "mytpen-auth/components";
import { getCurrentPathname } from "mytpen-auth/nextjs";

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  // Pathname for callbacks: `getCurrentPathname()` reads the `x-matched-path` header when
  // `mytpenMiddleware` from `mytpen-auth/nextjs` is installed; without middleware it falls back to "/".
  const pathname = await getCurrentPathname();

  // publicRoutes: only these paths skip the OAuth redirect when not signed in.
  // Use publicRoutes={["/"]} for a minimal app where home is public.
  // The repo demo lists paths like /account, /test, /exam/:id and leaves "/" protected.

  return (
    <html lang="en">
      <body>
        <MytpenAuthProvider
          providerId={process.env.MYTPEN_PROVIDER_ID!}
          callbackURL={pathname}
          newUserUrl={pathname}
          publicRoutes={["/", "/about"]}
          checkSubscription={true}
          billingURL={process.env.MYTPEN_AUTH_SERVER!}
          postSignOutRedirect={process.env.MYTPEN_AUTH_SERVER!}
          logo={<LogoSvg width={64} height={64} />}
        >
          {children}
        </MytpenAuthProvider>
      </body>
    </html>
  );
}

Adjust publicRoutes to your app: any route not listed may trigger the OAuth flow when autoRedirect is true (default). The demo app uses a specific list (e.g. /account, /test, /exam/:id) and does not put / in publicRoutes, so the home page requires sign-in.

Step 7: Use Authentication in Components

// app/page.tsx or any component
"use client";
import { useMytpenAuth, useSession } from "mytpen-auth/provider"; // ⚡ Cached, no repeated API calls

export default function Home() {
  const { session, isPending } = useSession(); // ⚡ Cached session, no network calls!
  const { signOut } = useMytpenAuth();

  if (isPending) return <div>Loading...</div>;

  if (session) {
    return (
      <div>
        <h1>Welcome, {session.user.name}!</h1>
        <button onClick={() => signOut()}>Sign Out</button>
      </div>
    );
  }

  // On routes in `publicRoutes` only: unauthenticated users see this instead of being redirected to OAuth.
  return <div>Public content — no login required on this route.</div>;
}

That's it! 🎉 Your app now has authentication with:

  • Auto-configured - reads from environment variables
  • Public routes support - some pages don't require authentication
  • Optimized session management - no repeated network calls
  • Beautiful loading screens with MYTPEN logo
  • Default scopes - includes subscription:read for MYTPEN subscription claims on the user
  • Subscription checking - automatic subscription validation (optional)
  • Redis secondary storage — the bundled auth uses Upstash Redis (Redis.fromEnv()); set the env vars from Step 3
  • TypeScript support

🚀 Redis Benefits

When you set up Redis (Step 3), Better Auth uses it as secondary storage (sessions and related keys):

  • ⚡ Performance: Shared, fast storage for session-related data
  • 📈 Scalability: Storage that scales with your Redis deployment
  • 🔄 Durability: Data survives individual Node process restarts (subject to Redis TTL/persistence settings)

How it works: src/auth.ts wires secondaryStorage to Upstash Redis. Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN (or compatible vars your host maps to them) so Redis.fromEnv() can connect.

Get access token (Dashboard / resource APIs)

Client (browser): use authClient.getAccessToken from mytpen-auth/client with your app’s OAuth providerId. The providerId must match MYTPEN_PROVIDER_ID and MytpenAuthProvider’s providerId (in client components expose it as NEXT_PUBLIC_MYTPEN_PROVIDER_ID or pass the same string as in your .env). Mismatched ids break token refresh and OAuth account lookup. Expired tokens are refreshed when possible.

"use client";
import { authClient } from "mytpen-auth/client";

async function getToken() {
  const { data } = await authClient.getAccessToken({
    providerId: process.env.NEXT_PUBLIC_MYTPEN_PROVIDER_ID!, // or your known app id string
  });
  return data?.accessToken;
}
// app/api/example/route.ts — use in a Route Handler or server action
import { headers } from "next/headers";
import { NextResponse } from "next/server";
import { auth } from "mytpen-auth";

export async function GET() {
  try {
    const providerId = process.env.MYTPEN_PROVIDER_ID;
    if (!providerId) {
      return NextResponse.json(
        { error: "MYTPEN_PROVIDER_ID environment variable not set" },
        { status: 500 },
      );
    }

    const h = await headers();
    const session = await auth.api.getSession({ headers: h });
    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const accessToken = await auth.api.getAccessToken({
      body: { providerId },
      headers: h,
    });

    return NextResponse.json({ accessToken });
  } catch (error) {
    console.error("AccessToken error:", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 },
    );
  }
}

🔑 One-Time Tokens

Generate and verify secure temporary tokens for QR codes, email verification, etc.

Configuration: The published auth uses oneTimeToken({ expiresIn: 10, storeToken: "hashed" }). To change it, copy src/auth.ts from this package into your app and adjust the plugin options (Advanced Usage).

Client-Side Usage

import { authClient } from "mytpen-auth/client";

// Generate token
const { data } = await authClient.oneTimeToken.generate();

// Verify token


const { data } = await authClient.oneTimeToken.verify({
  token: "your-token-here"
});

// POST /api/auth/one-time-token/verify — body: { token: "..." }

Server-Side Usage

import { auth } from "mytpen-auth";
import { headers } from "next/headers";

// Generate token
const data = await auth.api.generateOneTimeToken({
    // This endpoint requires session cookies.
    headers: await headers(),
});

// Verify token
const data = await auth.api.verifyOneTimeToken({
    body: {
        token: "some-token", // required
    },
});

💳 Subscription (user fields from OIDC)

Subscription state is not loaded from a separate billing HTTP API. The MYTPEN IdP maps claims into user columns via mapProfileToUser in the server auth config (subscriptionId, hasActiveSubscription, subscriptionEndAt, trialEndAt). They appear on session.user in API and client session payloads.

How it works

  • OIDC + scope: Request subscription:read (included in the package defaults) so the provider can return subscription-related claims.
  • User record: Values are stored as user additionalFields and returned with getSession / useSession.
  • Access gate: MytpenAuthProvider with checkSubscription={true} allows the user if userHasSubscriptionAccess(user) is true: paid flag (hasActiveSubscription as true / "true"), or trialEndAt is a valid date in the future, or subscriptionEndAt is a valid date in the future.

Client usage

import { useSession } from "mytpen-auth/provider";
import { userHasSubscriptionAccess, userHasActiveSubscription } from "mytpen-auth";

const { session } = useSession();
if (session && userHasSubscriptionAccess(session.user)) {
  // Paid, active trial, or subscription period not ended
}

// Flag-only check (ignores trial / subscription end dates):
if (session && userHasActiveSubscription(session.user.hasActiveSubscription)) {
  // ...
}

Server usage

import { auth, userHasSubscriptionAccess } from "mytpen-auth";
import { headers } from "next/headers";

const session = await auth.api.getSession({ headers: await headers() });
if (!session) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

if (!userHasSubscriptionAccess(session.user)) {
  return NextResponse.json({ error: "No activated plan" }, { status: 402 });
}

Optional: automatic redirect to billing

<MytpenAuthProvider
  providerId={process.env.MYTPEN_PROVIDER_ID!}
  checkSubscription={true}
  billingURL={process.env.MYTPEN_AUTH_SERVER ?? "https://dashboard.mytpen.app"}
>
  {children}
</MytpenAuthProvider>
  • Redirects when userHasSubscriptionAccess(user) is false (paid flag and both end dates considered).
  • Sends users to {billingURL}/billing?app-id={providerId} and signs them out first.
FieldDescription
subscriptionIdProvider subscription id (if any)
hasActiveSubscriptionBoolean or string from IdP; userHasActiveSubscription() normalizes the flag only
subscriptionEndAtISO string or null; if in the future, userHasSubscriptionAccess() is true
trialEndAtISO string or null; if in the future, userHasSubscriptionAccess() is true

Use userHasSubscriptionAccess(session.user) for the same rule as MytpenAuthProvider’s checkSubscription gate. For UI only, subscriptionEndDateInFuture(date) matches the trial/subscription end checks inside that helper.

🛡️ Optional: Add Middleware (Advanced)

If you need server-side route protection, add middleware:

Option 1: Simple Usage (Clerk-style)

// middleware.ts
import { mytpenMiddleware } from 'mytpen-auth/nextjs'

// Uses default configuration:
// - publicRoutes: ["/"]
// - protectedRoutes: ["/dashboard/*", "/profile/*", "/settings/*"]
// - loginUrl: "https://dashboard.mytpen.app/sign-in"
// - fallbackUrl: "/dashboard"
export default mytpenMiddleware()

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ]
}

Option 2: Custom Configuration

// middleware.ts
import { mytpenMiddleware } from 'mytpen-auth/nextjs'

export default mytpenMiddleware({
  publicRoutes: ["/", "/about", "/contact", "/exam/:id", "/:slug/public"],
  protectedRoutes: ["/dashboard/*", "/profile/*", "/settings/:id"],
  // loginUrl defaults to "https://dashboard.mytpen.app/sign-in"
  fallbackUrl: "/dashboard"
})

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ]
}

Option 3: Compose with Other Middleware

// middleware.ts
import { NextRequest } from 'next/server'
import { mytpenMiddleware } from 'mytpen-auth/nextjs'

import { NextResponse } from "next/server";

export async function middleware(request: NextRequest) {
  const authResponse = await mytpenMiddleware()(request);
  if (authResponse) return authResponse;

  // Your other middleware logic here

  return NextResponse.next();
}

export const config = {
  matcher: [
    // Skip Next.js internals and all static files, unless found in search params
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Always run for API routes
    '/(api|trpc)(.*)',
  ]
}

Route Pattern Matching

The middleware supports three types of route patterns:

1. Exact Match

publicRoutes: ["/", "/about", "/contact"]
// Matches exactly: /about
// Does NOT match: /about/team

2. Wildcard Pattern (*)

publicRoutes: ["/blog/*", "/docs/*"]
// Matches: /blog/post-1, /blog/post-2/comments
// Matches: /docs/intro, /docs/api/reference

3. Dynamic Parameters (:param)

publicRoutes: [
  "/exam/:id",           // Matches: /exam/123, /exam/abc
  "/post/:slug/view",    // Matches: /post/hello/view, /post/world/view
  "/:category/items",    // Matches: /electronics/items, /books/items
]

Example combining all patterns:

export default mytpenMiddleware({
  publicRoutes: [
    "/",                    // Exact: home page
    "/about",               // Exact: about page
    "/blog/*",              // Wildcard: all blog routes
    "/exam/:id",            // Dynamic: specific exam by ID
    "/user/:userId/profile" // Dynamic: user profile by ID
  ],
  protectedRoutes: [
    "/dashboard/*",         // Wildcard: all dashboard routes
    "/settings/:section"    // Dynamic: settings sections
  ]
})

Dynamic pathname access

mytpenMiddleware sets the incoming request header x-matched-path to the current pathname. Use getCurrentPathname() from mytpen-auth/nextjs in Server Components, or read x-matched-path yourself. If middleware is not installed for a route, the header may be missing and getCurrentPathname() falls back to "/".

// app/layout.tsx
import { getCurrentPathname } from 'mytpen-auth/nextjs'

export default async function RootLayout({ children }) {
  const pathname = await getCurrentPathname();
  
  return (
    <MytpenAuthProvider
      providerId={process.env.MYTPEN_PROVIDER_ID!}
      callbackURL={pathname}    // Redirect to current page after auth
      newUserUrl={pathname}     // Keep new users on current page
      publicRoutes={["/exam/:id", "/"]}
    >
      {children}
    </MytpenAuthProvider>
  );
}

Benefits:

  • ✅ Dynamic callbacks based on current route
  • ✅ Better UX: Users stay on the same page after authentication
  • ✅ Works with dynamic routes like /exam/:id
  • getCurrentPathname() reads the x-matched-path header set by mytpenMiddleware

Alternative: read the header yourself

import { headers } from "next/headers";

export default async function Layout({ children }) {
  const headersList = await headers();
  const pathname = headersList.get("x-matched-path") || "/";
  // ...
}

Middleware Configuration Options

OptionTypeDefaultDescription
publicRoutesstring[]["/"]Routes accessible without authentication. Supports exact matches, wildcards (/blog/*), and dynamic params (/exam/:id)
protectedRoutesstring[]["/dashboard/*", "/profile/*", "/settings/*"]Routes that require authentication
loginUrlstring"https://dashboard.mytpen.app/sign-in"Where to redirect unauthenticated users
fallbackUrlstring"/dashboard"Where to redirect authenticated users who try to access the login page

Example: Custom Login URL

If you have your own login page:

export default mytpenMiddleware({
  publicRoutes: ["/", "/sign-in", "/sign-up"],
  loginUrl: "/sign-in", // Your custom login page
  fallbackUrl: "/dashboard"
})

Example: Use MYTPEN Dashboard Login (Default)

export default mytpenMiddleware({
  publicRoutes: ["/", "/about"],
  // loginUrl will default to "https://dashboard.mytpen.app/sign-in"
  fallbackUrl: "/dashboard"
})

📦 Package exports

These match the exports field in package.json: mytpen-auth, mytpen-auth/client, mytpen-auth/server, mytpen-auth/edge, mytpen-auth/components, mytpen-auth/provider, mytpen-auth/nextjs.

SubpathMain exports
mytpen-authauth; subscription helpers userHasActiveSubscription, userHasSubscriptionAccess, subscriptionEndDateInFuture; types MytpenClientAuthConfig, IMytpenSession; toNextJsHandler, getSessionCookie, getCookieCache; IdP URL helpers resolveMytpenIdpAuthApiBase, resolveMytpenIdpOAuthRevokeUrl, resolveMytpenIdpOrigin, resolveMytpenIdpUserInfoUrl
mytpen-auth/serverSame preconfigured auth as the root entry (alias for apps that prefer a /server import).
mytpen-auth/clientauthClient, createMytpenAuthClient
mytpen-auth/edgegetSessionCookie, getCookieCache (Edge-safe; no Node-only APIs)
mytpen-auth/componentsLogoSvg, LoaderSvg, LoadingScreen, AuthLoadingScreen
mytpen-auth/providerMytpenAuthProvider, useMytpenAuth, useSession, ProtectedRoute
mytpen-auth/nextjsmytpenMiddleware, getCurrentPathname; type MytpenMiddlewareConfig

Better Auth server plugins used inside auth (OAuth revoke on sign-out, custom endpoints, etc.) live under src/plugins and are not re-exported—use import { auth } from "mytpen-auth" or fork src/auth.ts if you need a custom betterAuth setup.

For extra providers or a fully custom server config, define your own betterAuth({ ... }) (see better-auth/minimal) and copy/adapt options from this repo’s src/auth.ts.

Session hooks

  • useSession from mytpen-auth/provider — reads session from the provider context (same source as useMytpenAuth); use this in app UI under MytpenAuthProvider.
  • authClient.useSession() from mytpen-auth/client — Better Auth’s hook on the raw client; use when you are not inside the provider or need the client’s default behavior.

useSession return value:

const { 
  session,        // User session object or null
  isPending,      // true when loading session
  error           // Error object if authentication failed
} = useSession();

Advanced Hook - useMytpenAuth:

For advanced use cases where you need access to the auth client or signOut function:

import { useMytpenAuth } from "mytpen-auth/provider";

function MyComponent() {
  const { 
    session,      // User session
    isPending,    // Loading state
    error,        // Error state
    authClient,   // Raw Better Auth client
    signOut       // Sign out function with custom redirect
  } = useMytpenAuth();
  
  const handleSignOut = async () => {
    await signOut("https://custom-redirect.com");
  };

  // Default redirect matches billingURL (e.g. MYTPEN_AUTH_SERVER) unless you set postSignOutRedirect.
  const handleSignOutWithDefaults = async () => {
    await signOut();
  };

  return <button onClick={handleSignOut}>Sign Out</button>;
}

OAuth2 revoke on sign-out: Keep client_secret on the server only. The bundled auth runs a before hook on POST /sign-out: it reads the encrypted refresh token from the account row, sends a best-effort RFC 7009 revoke to {MYTPEN_AUTH_SERVER}/api/auth/oauth2/revoke (default host: https://dashboard.mytpen.app), then clears OAuth fields locally. The browser only calls signOut() / authClient.signOut(); refresh tokens are not exposed to the client.

Optional: set MYTPEN_DEBUG_IDP_REVOKE=true on the server to log revoke URL / skip reasons.

🎯 Key Features

  • 🚀 Ultra-Minimal Setup: Seven steps to get authentication working
  • 🤖 Auto-Configuration: Reads from environment variables - no manual config needed
  • 🎨 Beautiful UI: Built-in loading screens with MYTPEN branding (fully customizable)
  • ⚡ Performance: Cached session management prevents repeated API calls
  • 🛡️ Flexible Protection: Support for public routes and optional middleware
  • 🔧 Clerk-style API: Familiar patterns if you've used Clerk
  • 📱 TypeScript: Full type safety out of the box
  • 🌐 OIDC Standard: Works with MYTPEN's OpenID Connect implementation
  • 🎛️ Smart Defaults: Default scopes, auto-detected base URLs, and more
  • 🎭 Customizable: Custom loading screens, messages, and callbacks

🎨 Customization

Custom Loading Screen

You can customize the loading screen that appears during authentication:

// app/layout.tsx
import { MytpenAuthProvider } from "mytpen-auth/provider";
import { LogoSvg } from "mytpen-auth/components";

// Option 1: Custom messages
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <MytpenAuthProvider
          providerId={process.env.MYTPEN_PROVIDER_ID!}
          callbackURL="/dashboard"
          publicRoutes={["/"]}
          logo={<LogoSvg width={64} height={64} />}
          pendingMessage="Checking authentication..."
          redirectingLoadingMessage="Checking your sign-in status..."
        >
          {children}
        </MytpenAuthProvider>
      </body>
    </html>
  );
}

// Option 2: Completely custom loading component
function CustomLoader() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="text-center">
        <h1>Custom Loading Screen</h1>
        <p>Please wait...</p>
      </div>
    </div>
  );
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <MytpenAuthProvider
          providerId={process.env.MYTPEN_PROVIDER_ID!}
          callbackURL="/dashboard"
          publicRoutes={["/"]}
          loadingComponent={<CustomLoader />}
        >
          {children}
        </MytpenAuthProvider>
      </body>
    </html>
  );
}

Protected Route Component

For fine-grained route protection, you can use the ProtectedRoute component:

// app/dashboard/page.tsx
"use client";
import { ProtectedRoute } from "mytpen-auth/provider";

export default function DashboardPage() {
  return (
    <ProtectedRoute 
      redirectTo="/login"
      fallback={<div>Loading...</div>}
    >
      <div>
        <h1>Protected Dashboard</h1>
        <p>Only authenticated users can see this</p>
      </div>
    </ProtectedRoute>
  );
}

ProtectedRoute Props:

PropTypeDefaultDescription
redirectTostring"/auto-login"Where to redirect if not authenticated
fallbackReact.ReactNode<AuthLoadingScreen />Component to show while checking auth
childrenReact.ReactNodeRequiredContent to show when authenticated

Provider Configuration Options

The MytpenAuthProvider accepts the following props:

PropTypeDefaultDescription
providerIdstringRequiredYour MYTPEN OAuth provider ID
callbackURLstring"/dashboard"Where to redirect after successful login
newUserUrlstring"/dashboard"Where to redirect new users
errorUrlstring"/error"Where to redirect on authentication error
scopesstring[]["openid", "profile", "email", "offline_access", "subscription:read"]OAuth scopes to request
publicRoutesstring[]["/"]Routes that don't require authentication
autoRedirectbooleantrueAutomatically redirect to login when not authenticated
logoReact.ReactNode<LogoSvg />Logo to display on loading screen
loadingComponentReact.ReactNodeundefinedCustom loading component (overrides all loading UI)
pendingMessagestring"Checking your sign-in status..."Message shown while checking session
redirectingLoadingMessagestring"Checking your sign-in status..."Message shown while redirecting / loading
checkMytpenLoggedInbooleantrueOn protected routes, one IdP GET /mytpen/is-logged-in check per load; signs out if the IdP flag is false
classNamestring""Additional CSS classes for loading screen
checkSubscriptionbooleantrueRedirect to billing when userHasSubscriptionAccess(user) is false
billingURLstring"https://dashboard.mytpen.app"Base URL for billing redirect (/billing?app-id=...)
postSignOutRedirectstringbillingURLRedirect after signOut() when no custom URL is passed
onError(error: Error) => voidundefinedCallback when authentication error occurs
onSuccess(session: any) => voidundefinedCallback when authentication succeeds

🔧 Advanced Usage

Custom client baseURL

// lib/auth-client.ts
import { createMytpenAuthClient } from 'mytpen-auth/client';

export const { authClient } = createMytpenAuthClient({
  baseURL: process.env.MYTPEN_AUTH_BASEURL!,
});

Use authClient.signIn.oauth2, authClient.signOut, authClient.useSession, etc. from Better Auth's client API.

Custom server auth (plugins, extra providers)

There is no createMytpenAuth factory. Either use import { auth } from "mytpen-auth" as-is, or maintain lib/auth.ts with betterAuth from better-auth/minimal by copying src/auth.ts from this package and editing plugins, trustedOrigins, or genericOAuth({ config: [...] }). See the Better Auth plugins directory for optional add-ons.

Related docs: OAuth 2.1 Provider (OIDC-compatible) · Generic OAuth

Server-side session access

import { auth } from "mytpen-auth";
import { headers } from "next/headers";

export async function GET() {
  const session = await auth.api.getSession({
    headers: await headers(),
  });
  if (!session) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }
  return Response.json({ userId: session.user.id });
}

If you use a custom lib/auth.ts, import auth from there instead.

Edge Runtime Compatibility

// For middleware or edge functions
import { getSessionCookie } from 'mytpen-auth/edge';

const session = getSessionCookie(request);

📚 Demo app (demo/)

The demo Next.js app exercises the same APIs you use in production. Run it from the repo root with pnpm dev:demo (or cd demo && pnpm dev). The demo dev server listens on port 3001 by default — set MYTPEN_AUTH_BASEURL / BETTER_AUTH_URL to http://localhost:3001 for OAuth callbacks.

Environment: copy demo/.example.env to demo/.env and fill OAuth values from the MYTPEN dashboard. Redis uses UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN like the main setup steps.

Layout (app/layout.tsx): getCurrentPathname() from mytpen-auth/nextjs, MytpenAuthProvider with providerId, dynamic callbackURL / newUserUrl, publicRoutes (currently ["/account", "/test", "/exam/:id", "/home"]/ is not public), billingURL and postSignOutRedirect from MYTPEN_AUTH_SERVER, checkSubscription, and default checkMytpenLoggedIn (true unless you override it).

Client pages: useMytpenAuth, useSession, signOut, and authClient from mytpen-auth/client — e.g. getAccessToken / refreshToken with NEXT_PUBLIC_MYTPEN_PROVIDER_ID (same as MYTPEN_PROVIDER_ID), mytpen.isLoggedIn(), mytpen.oauth2.userinfo (Bearer access token), accountInfo, and oneTimeToken.generate / verify on the QR page.

Route handlers: GET /api/user uses auth.api.getSession plus userHasSubscriptionAccess, userHasActiveSubscription, subscriptionEndDateInFuture. GET /api/user-info-auth-server uses auth.api.getAccessToken and auth.api.userinfo.

The demo does not ship a middleware.ts; without middleware, getCurrentPathname() falls back to "/". Add mytpenMiddleware from mytpen-auth/nextjs if you need pathname headers and route protection.

🆘 Support

📄 License

MIT License - see LICENSE file for details.

Keywords

mytpen

FAQs

Package last updated on 29 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts