Skip to main content

Logout

Why proper logout matters 🔒​

A secure logout process is essential for protecting user data, especially on shared devices. This guide shows how to properly terminate sessions by clearing tokens and optionally revoking them server-side.

tip

Make sure you've completed the Quick Start and understand Token Management before implementing logout functionality.


1 · Client-side logout (clearing tokens)​

The new SDK provides built-in methods for clearing authentication state. When implementing logout, the Account Management token is the primary security concern since it identifies the specific user.

Using the SDK's auth methods​

import React from "react";
import { useNavigate } from "react-router-dom";
import { createShopperClient } from "@epcc-sdk/sdks-shopper";

const { auth } = createShopperClient(
{ baseUrl: "https://useast.api.elasticpath.com" },
{ clientId: process.env.NEXT_PUBLIC_CLIENT_ID }
);

const LogoutButton: React.FC = () => {
const navigate = useNavigate();

const handleLogout = async () => {
// Clear authentication using SDK
await auth.clear();

// Also clear account tokens (not managed by SDK)
localStorage.removeItem("ep_account_token");
localStorage.removeItem("ep_account_refresh_token");

// Redirect to login page
navigate("/login");
};

return (
<button onClick={handleLogout} className="logout-button">
Sign Out
</button>
);
};

export default LogoutButton;

Manual token clearing​

If you need more control:

import React from "react";
import { useNavigate } from "react-router-dom";

const ManualLogoutButton: React.FC = () => {
const navigate = useNavigate();

const handleLogout = () => {
// Clear all authentication tokens
const authKeys = [
"ep_implicit_token",
"ep_account_token",
"ep_account_refresh_token",
"_store_ep_credentials" // SDK's default storage key
];

authKeys.forEach(key => localStorage.removeItem(key));

// Redirect to login page
navigate("/login");
};

return (
<button onClick={handleLogout} className="logout-button">
Sign Out
</button>
);
};

When using cookies with the SDK:

import React from "react";
import { useNavigate } from "react-router-dom";
import { createShopperClient } from "@epcc-sdk/sdks-shopper";

// Configure client with cookie storage
const { auth } = createShopperClient(
{ baseUrl: "https://useast.api.elasticpath.com" },
{
clientId: process.env.NEXT_PUBLIC_CLIENT_ID,
storage: "cookie"
}
);

const CookieLogoutButton: React.FC = () => {
const navigate = useNavigate();

const handleLogout = async () => {
// SDK clears its own cookies
await auth.clear();

// Clear any additional cookies
document.cookie =
"ep_account_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";
document.cookie =
"ep_account_refresh_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";

// Redirect to login page
navigate("/login");
};

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

export default CookieLogoutButton;

2 · Server-side logout (for HttpOnly cookies)​

For maximum security with SSR applications:

// Client-side React component
import React from "react";
import { useNavigate } from "react-router-dom";

const ServerLogoutButton: React.FC = () => {
const navigate = useNavigate();
const [isLoggingOut, setIsLoggingOut] = React.useState(false);

const handleLogout = async () => {
setIsLoggingOut(true);

try {
// Call your backend logout endpoint
await fetch("/api/logout", {
method: "POST",
credentials: "include", // Important to include cookies
});

// Redirect to login page after server confirms logout
navigate("/login");
} catch (error) {
console.error("Logout failed:", error);
// Fallback: redirect anyway
navigate("/login");
} finally {
setIsLoggingOut(false);
}
};

return (
<button onClick={handleLogout} disabled={isLoggingOut}>
{isLoggingOut ? "Signing out..." : "Sign Out"}
</button>
);
};

export default ServerLogoutButton;

Server-side implementation (Next.js App Router example)​

// app/api/logout/route.ts
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function POST() {
const cookieStore = cookies();

// Clear all auth cookies
cookieStore.delete("ep_implicit_token");
cookieStore.delete("ep_account_token");
cookieStore.delete("ep_account_refresh_token");
cookieStore.delete("_store_ep_credentials"); // SDK's cookie

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

Note on token invalidation

Elastic Path currently does not offer a direct API endpoint to revoke account tokens. The most secure approach is to:

  1. Clear all tokens from client storage
  2. For server-rendered applications, clear any session state on your server
  3. For added security with HttpOnly cookies, set an immediate expiry on the server

3 · Handling logout across multiple tabs​

The SDK automatically synchronizes authentication state across tabs when using localStorage. However, you may want to enhance the UI response:

While localStorage is shared across all tabs from the same domain (meaning tokens removed in one tab are removed everywhere), other tabs won't automatically detect this change and update their UI state unless specifically notified. Here's how to synchronize logout UI state across tabs:

import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

const MultiTabLogoutButton: React.FC = () => {
const navigate = useNavigate();
const [isAuthenticated, setIsAuthenticated] = useState(true);

// Listen for logout events from other tabs
useEffect(() => {
const checkAuth = () => {
const accountToken = localStorage.getItem("ep_account_token");
setIsAuthenticated(!!accountToken);

// If no token found, redirect to login
if (!accountToken && isAuthenticated) {
navigate("/login");
}
};

// Initial check
checkAuth();

// Event handler for storage changes
const handleStorageChange = (event: StorageEvent) => {
// When another tab triggers logout by setting this flag
if (event.key === "ep_user_logged_out") {
checkAuth();
}
};

window.addEventListener("storage", handleStorageChange);

return () => {
window.removeEventListener("storage", handleStorageChange);
};
}, [navigate, isAuthenticated]);

const handleLogout = () => {
// Clear tokens (will affect all tabs)
localStorage.removeItem("ep_account_token");
localStorage.removeItem("ep_implicit_token");

// Notify other tabs about logout
localStorage.setItem("ep_user_logged_out", Date.now().toString());

// Update local state and navigate
setIsAuthenticated(false);
navigate("/login");
};

return (
<button onClick={handleLogout} disabled={!isAuthenticated}>
Sign Out
</button>
);
};

export default MultiTabLogoutButton;
info

The key point here isn't about clearing the tokens (which are automatically shared), but rather notifying other tabs to update their UI state in response to the tokens being removed. This synchronization helps provide a consistent experience across all open tabs.

4 · Automatically logging out inactive users​

Combine the SDK's auth methods with activity monitoring:

For security-sensitive applications, you might want to automatically log out users after a period of inactivity:

import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { createShopperClient } from "@epcc-sdk/sdks-shopper";

const INACTIVITY_LIMIT = 30 * 60 * 1000; // 30 minutes

// Get auth instance
const { auth } = createShopperClient(
{ baseUrl: "https://useast.api.elasticpath.com" },
{ clientId: process.env.NEXT_PUBLIC_CLIENT_ID }
);

const InactivityMonitor: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const navigate = useNavigate();

useEffect(() => {
let inactivityTimeout: number | undefined;

// Function to logout and redirect
const logout = async () => {
// Use SDK to clear authentication
await auth.clear();

// Clear account tokens
localStorage.removeItem("ep_account_token");
localStorage.removeItem("ep_account_refresh_token");

navigate("/login");
};

// Reset the timer on user activity
const resetTimer = () => {
if (inactivityTimeout) window.clearTimeout(inactivityTimeout);
inactivityTimeout = window.setTimeout(logout, INACTIVITY_LIMIT);
};

// Monitor user activity
const events = [
"mousedown",
"mousemove",
"keypress",
"scroll",
"touchstart",
];

events.forEach((event) => {
document.addEventListener(event, resetTimer, true);
});

// Initial timer setup
resetTimer();

// Cleanup
return () => {
if (inactivityTimeout) window.clearTimeout(inactivityTimeout);
events.forEach((event) => {
document.removeEventListener(event, resetTimer, true);
});
};
}, [navigate]);

return <>{children}</>;
};

// Usage in your app
const App: React.FC = () => {
return (
<InactivityMonitor>
<YourApp />
</InactivityMonitor>
);
};

export default App;
info

When considering which tokens to clear during logout, remember:

  • Account Management token - This is the critical one that represents user identity and must always be cleared during logout
  • Implicit token - This functions more like a public API key to access the store and could technically be preserved, but it's generally cleaner to clear it as well

5 · Comprehensive logout service​

For applications that need centralized logout logic:

import { createShopperClient } from "@epcc-sdk/sdks-shopper";

class LogoutService {
private auth: any;

constructor() {
const { auth } = createShopperClient(
{ baseUrl: process.env.NEXT_PUBLIC_EPCC_BASE_URL },
{ clientId: process.env.NEXT_PUBLIC_CLIENT_ID }
);
this.auth = auth;
}

async logout() {
try {
// Clear SDK authentication
await this.auth.clear();

// Clear account tokens
this.clearAccountTokens();

// Clear any app-specific data
this.clearAppData();

// Notify other tabs
this.notifyOtherTabs();

return { success: true };
} catch (error) {
console.error("Logout error:", error);
// Even on error, try to clear what we can
this.clearAccountTokens();
throw error;
}
}

private clearAccountTokens() {
const accountKeys = [
"ep_account_token",
"ep_account_refresh_token",
"ep_user_id",
"ep_user_email"
];
accountKeys.forEach(key => localStorage.removeItem(key));
}

private clearAppData() {
// Clear any app-specific data
sessionStorage.clear();
}

private notifyOtherTabs() {
// Use broadcast channel for cross-tab communication
const channel = new BroadcastChannel("auth_channel");
channel.postMessage({ type: "logout" });
channel.close();
}
}

// Export singleton instance
export const logoutService = new LogoutService();

6 · Best practices​

  • Use SDK methods: Leverage auth.clear() for proper cleanup
  • Clear all auth data: Remember both implicit and account tokens
  • Provide visual feedback: Show users that logout is in progress/complete
  • Handle errors gracefully: Always attempt cleanup even if some steps fail
  • Consider security levels: Use HttpOnly cookies for high-security applications
  • Test multi-tab behavior: Ensure consistent logout across browser tabs

For complete implementations, check out the authentication examples in the Composable Frontend repository.