Skip to main content
ETN Identity SDK Logo

ETN Identity SDK

Version 1.0.0 | Released: January 13, 2026

"The Official React SDK for Integrating ETN Identity."

ETN EcosystemBuilt on TONDocumentationETN Learn

Follow on XSubscribe on YouTubeFollow on TikTokCommunity Chat on TelegramAnnouncements on Telegram

GeckoTerminal Price ChartStake on jVaultJetton Master on TonviewerDonate TON

ETN Identity SDK Documentation

Overview

This SDK helps developers integrate "Sign in with ETN" into their applications. It follows the OIDC Authorization Code Flow with Token Rotation, which is the standard security pattern used in the ETN Ecosystem (e.g., ETN Vibe).

The integration consists of two parts:

  1. Frontend (Client): Redirecting the user to the ETN Identity Provider.
  2. Backend (Server): Exchanging the code for tokens and managing the session.

Installation

npm install @etn-ecosystem/identity-sdk iron-session

(Note: iron-session is recommended for secure cookie management).

1. Client-Side: Redirect to Login

Use the SDK to construct the authorization URL.

// src/lib/auth-client.ts
import { ETNAuthClient } from '@etn-ecosystem/identity-sdk';

export const authClient = new ETNAuthClient({
clientId: process.env.ETN_CLIENT_ID!,
redirectUri: process.env.ETN_REDIRECT_URI!,
// Default scope is 'openid profile offline_access'
});

export function signIn() {
// Generate a random state for CSRF protection
const state = crypto.randomUUID();
const url = authClient.buildAuthorizeUrl(state);
window.location.href = url; // Or use Next.js redirect()
}

2. Server-Side: Handle Callback & Exchange Tokens

Create a route handler (e.g., app/api/auth/callback/route.ts in Next.js) to handle the redirect from ETN Identity.

// app/api/auth/callback/route.ts
import { getSession } from '@/lib/session'; // See Step 3
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
const { code } = await req.json(); // Or get from searchParams if GET

// Exchange Code for Tokens
const tokenResponse = await fetch('https://auth.etnecosystem.org/api/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: code,
client_id: process.env.ETN_CLIENT_ID,
client_secret: process.env.ETN_CLIENT_SECRET, // NEVER expose this to client
redirect_uri: process.env.ETN_REDIRECT_URI,
}),
});

const tokens = await tokenResponse.json();

if (tokens.error) {
return NextResponse.json({ error: tokens.error_description }, { status: 400 });
}

// Save to Session
const session = await getSession();
session.isLoggedIn = true;
session.token = tokens.access_token;
session.refreshToken = tokens.refresh_token;
// Calculate expiration: Now + expires_in - 1 minute buffer
session.expiresAt = Date.now() + (tokens.expires_in * 1000) - 60000;

await session.save();

return NextResponse.json({ success: true });
}

3. Session Management & Token Rotation

It is critical to implement Token Rotation to keep the user logged in securely. The Access Token expires in 1 hour; use the Refresh Token to get a new one without forcing the user to log in again.

Create a helper src/lib/session.ts (using iron-session):

import { getIronSession } from 'iron-session';
import { cookies } from 'next/headers';

// ... Session Data Interface ...

export async function getValidSession({ redirectOnExpire = false } = {}) {
const session = await getSession();

if (!session.isLoggedIn || !session.expiresAt) return session;

// Check if Access Token is expired
if (Date.now() > session.expiresAt) {
if (redirectOnExpire) {
// In Server Components, we cannot set cookies, so we redirect to a route that can.
const { redirect } = await import('next/navigation');
redirect('/api/auth/refresh');
}

// Attempt Refresh (Server Actions / Route Handlers only)
try {
const res = await fetch("https://auth.etnecosystem.org/api/v1/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "refresh_token",
refresh_token: session.refreshToken,
client_id: process.env.ETN_CLIENT_ID,
client_secret: process.env.ETN_CLIENT_SECRET,
}),
});

const newTokens = await res.json();
// Update Session with NEW tokens
session.token = newTokens.access_token;
if (newTokens.refresh_token) session.refreshToken = newTokens.refresh_token;
session.expiresAt = Date.now() + (newTokens.expires_in * 1000) - 60000;
await session.save();
} catch (e) {
session.isLoggedIn = false;
// Handle error
}
}
return session;
}

4. Protecting Pages

In your Protected Pages (Server Components), always call getValidSession with redirectOnExpire: true.

// app/dashboard/page.tsx
export default async function Dashboard() {
// This will check expiration and redirect to refresh if needed
const session = await getValidSession({ redirectOnExpire: true });

if (!session.isLoggedIn) redirect('/');

// ...
}