From e03def996d0a25318dc4c3e212c35aea68890362 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Thu, 2 Oct 2025 14:25:01 +0100 Subject: [PATCH 1/3] Update and enable Auth JWT example --- .../javascript/src/config.ts | 3 +++ .../javascript/src/script.ts | 3 ++- .../javascript/vite-env.d.ts | 1 + examples/auth-generate-jwt/react/src/App.tsx | 23 +++++++++++++++++-- .../auth-generate-jwt/react/src/config.ts | 3 +++ examples/auth-generate-jwt/react/src/env.d.ts | 1 + src/components/Examples/ExamplesRenderer.tsx | 2 ++ src/data/examples/index.ts | 11 +++++++++ 8 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 examples/auth-generate-jwt/javascript/src/config.ts create mode 100644 examples/auth-generate-jwt/react/src/config.ts diff --git a/examples/auth-generate-jwt/javascript/src/config.ts b/examples/auth-generate-jwt/javascript/src/config.ts new file mode 100644 index 0000000000..ce58979c72 --- /dev/null +++ b/examples/auth-generate-jwt/javascript/src/config.ts @@ -0,0 +1,3 @@ +export const config = { + AUTH_URL: import.meta.env.VITE_AUTH_JWT_URL as string, +}; diff --git a/examples/auth-generate-jwt/javascript/src/script.ts b/examples/auth-generate-jwt/javascript/src/script.ts index 8e22e7d33b..4b7b51edb1 100644 --- a/examples/auth-generate-jwt/javascript/src/script.ts +++ b/examples/auth-generate-jwt/javascript/src/script.ts @@ -1,5 +1,6 @@ import * as Ably from 'ably'; import './styles.css'; +import { config } from './config'; const connectButton = document.querySelector('button') as HTMLButtonElement; @@ -14,7 +15,7 @@ function handleConnect() { messageOne.textContent = '✓'; const realtimeClient = new Ably.Realtime({ - authUrl: 'http://localhost:3001/generate-jwt', + authUrl: config.AUTH_URL || 'http://localhost:3001/generate-jwt', }); const messageTwo = document.getElementById('message-2'); diff --git a/examples/auth-generate-jwt/javascript/vite-env.d.ts b/examples/auth-generate-jwt/javascript/vite-env.d.ts index 449e61aa75..ed602dc054 100644 --- a/examples/auth-generate-jwt/javascript/vite-env.d.ts +++ b/examples/auth-generate-jwt/javascript/vite-env.d.ts @@ -1,5 +1,6 @@ interface ImportMetaEnv { readonly VITE_ABLY_KEY: string; + readonly VITE_AUTH_URL: string; // Add other environment variables here if needed } diff --git a/examples/auth-generate-jwt/react/src/App.tsx b/examples/auth-generate-jwt/react/src/App.tsx index 110c1a55d9..8ed3fe2664 100644 --- a/examples/auth-generate-jwt/react/src/App.tsx +++ b/examples/auth-generate-jwt/react/src/App.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import * as Ably from 'ably'; import { AblyProvider, useConnectionStateListener } from 'ably/react'; import './styles/styles.css'; +import { config } from './config'; interface StatusMessage { id: number; @@ -53,10 +54,28 @@ export default function App() { ]); const handleConnect = async () => { - // Navigate to authenticated page - window.location.href = '/authenticated'; + // Update first message + setMessages((prevMessages) => prevMessages.map((msg) => (msg.id === 1 ? { ...msg, success: true } : msg))); + + // Initialize Ably client with JWT auth + const realtimeClient = new Ably.Realtime({ + authUrl: config.AUTH_URL || 'http://localhost:3001/generate-jwt', + }); + + // Update second message + setMessages((prevMessages) => prevMessages.map((msg) => (msg.id === 2 ? { ...msg, success: true } : msg))); + + setClient(realtimeClient); }; + if (client) { + return ( + + + + ); + } + return (
diff --git a/examples/auth-generate-jwt/react/src/config.ts b/examples/auth-generate-jwt/react/src/config.ts new file mode 100644 index 0000000000..ce58979c72 --- /dev/null +++ b/examples/auth-generate-jwt/react/src/config.ts @@ -0,0 +1,3 @@ +export const config = { + AUTH_URL: import.meta.env.VITE_AUTH_JWT_URL as string, +}; diff --git a/examples/auth-generate-jwt/react/src/env.d.ts b/examples/auth-generate-jwt/react/src/env.d.ts index 7421e8e743..ada17315d3 100644 --- a/examples/auth-generate-jwt/react/src/env.d.ts +++ b/examples/auth-generate-jwt/react/src/env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_ABLY_KEY: string; + readonly VITE_AUTH_JWT_URL: string; } interface ImportMeta { diff --git a/src/components/Examples/ExamplesRenderer.tsx b/src/components/Examples/ExamplesRenderer.tsx index 5db3648007..59d680edec 100644 --- a/src/components/Examples/ExamplesRenderer.tsx +++ b/src/components/Examples/ExamplesRenderer.tsx @@ -73,6 +73,8 @@ const ExamplesRenderer = ({ Object.entries(files).forEach(([languageKey, languageFiles]) => { result[languageKey as LanguageKey] = updateAblyConnectionKey(languageFiles, apiKey, { NAME: getRandomChannelName(), // Use CHANNEL_NAME as env var key + AUTH_TOKEN_URL: process.env.VITE_AUTH_TOKEN_URL || '', + AUTH_JWT_URL: process.env.VITE_AUTH_JWT_URL || '', }); }); return result; diff --git a/src/data/examples/index.ts b/src/data/examples/index.ts index b7d4ba13c7..2705fb6ade 100644 --- a/src/data/examples/index.ts +++ b/src/data/examples/index.ts @@ -4,6 +4,7 @@ export const DEFAULT_EXAMPLE_LANGUAGES = ['javascript', 'react']; export const examples: Example[] = [ { +<<<<<<< HEAD id: 'ai-transport-message-per-token', name: 'Message per token streaming', description: 'Stream AI responses token-by-token using the message-per-token pattern.', @@ -23,6 +24,16 @@ export const examples: Example[] = [ metaTitle: 'Build AI message-per-response streaming with Ably AI Transport', metaDescription: `Stream AI-generated tokens by appending them to a single message using Ably AI Transport. Each response appears as one compacted message in channel history.`, }, + { + id: 'auth-generate-jwt', + name: 'Generate JWT', + description: 'Generate a JSON Web Token (JWT) for authenticating users.', + products: ['auth'], + layout: 'single-horizontal', + visibleFiles: ['src/script.ts', 'App.tsx', 'Chat.tsx', 'Home.tsx', 'index.tsx'], + metaTitle: `Build chat history with Ably’s Chat SDK`, + metaDescription: `Learn how to build and use JWTs for client authentication with Ably. Secure token-based auth for realtime applications.`, + }, { id: 'chat-presence', name: 'Chat presence', From 14c187a40089f94e0ccd9d3967a8cade72bb37da Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Mon, 27 Oct 2025 12:08:46 +0000 Subject: [PATCH 2/3] Update and enable Auth Token example --- .../javascript/src/config.ts | 3 +++ .../javascript/src/script.ts | 3 ++- .../javascript/vite-env.d.ts | 1 + examples/auth-request-token/react/src/App.tsx | 23 +++++++++++++++-- .../auth-request-token/react/src/config.ts | 3 +++ .../auth-request-token/react/src/env.d.ts | 1 + src/data/examples/index.ts | 25 ++++++++++++++++--- 7 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 examples/auth-request-token/javascript/src/config.ts create mode 100644 examples/auth-request-token/react/src/config.ts diff --git a/examples/auth-request-token/javascript/src/config.ts b/examples/auth-request-token/javascript/src/config.ts new file mode 100644 index 0000000000..651f0a42de --- /dev/null +++ b/examples/auth-request-token/javascript/src/config.ts @@ -0,0 +1,3 @@ +export const config = { + AUTH_URL: import.meta.env.VITE_AUTH_TOKEN_URL as string, +}; diff --git a/examples/auth-request-token/javascript/src/script.ts b/examples/auth-request-token/javascript/src/script.ts index c7adc27b32..03a0ce5658 100644 --- a/examples/auth-request-token/javascript/src/script.ts +++ b/examples/auth-request-token/javascript/src/script.ts @@ -1,5 +1,6 @@ import * as Ably from 'ably'; import './styles.css'; +import { config } from './config'; const connectButton = document.querySelector('button') as HTMLButtonElement; @@ -14,7 +15,7 @@ function handleConnect() { messageOne.textContent = '✓'; const realtimeClient = new Ably.Realtime({ - authUrl: 'http://localhost:3001/request-token', + authUrl: config.AUTH_URL || 'http://localhost:3001/request-token', }); const messageTwo = document.getElementById('message-2'); diff --git a/examples/auth-request-token/javascript/vite-env.d.ts b/examples/auth-request-token/javascript/vite-env.d.ts index 449e61aa75..8affe3e165 100644 --- a/examples/auth-request-token/javascript/vite-env.d.ts +++ b/examples/auth-request-token/javascript/vite-env.d.ts @@ -1,5 +1,6 @@ interface ImportMetaEnv { readonly VITE_ABLY_KEY: string; + readonly VITE_AUTH_TOKEN_URL: string; // Add other environment variables here if needed } diff --git a/examples/auth-request-token/react/src/App.tsx b/examples/auth-request-token/react/src/App.tsx index 9e7c99d768..489639569b 100644 --- a/examples/auth-request-token/react/src/App.tsx +++ b/examples/auth-request-token/react/src/App.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import * as Ably from 'ably'; import { AblyProvider, useConnectionStateListener } from 'ably/react'; import './styles/styles.css'; +import { config } from './config'; interface StatusMessage { id: number; @@ -53,10 +54,28 @@ export default function App() { ]); const handleConnect = async () => { - // Navigate to authenticated page - window.location.href = '/authenticated'; + // Update first message + setMessages((prevMessages) => prevMessages.map((msg) => (msg.id === 1 ? { ...msg, success: true } : msg))); + + // Initialize Ably client with token auth + const realtimeClient = new Ably.Realtime({ + authUrl: config.AUTH_URL || 'http://localhost:3001/request-token', + }); + + // Update second message + setMessages((prevMessages) => prevMessages.map((msg) => (msg.id === 2 ? { ...msg, success: true } : msg))); + + setClient(realtimeClient); }; + if (client) { + return ( + + + + ); + } + return (
diff --git a/examples/auth-request-token/react/src/config.ts b/examples/auth-request-token/react/src/config.ts new file mode 100644 index 0000000000..651f0a42de --- /dev/null +++ b/examples/auth-request-token/react/src/config.ts @@ -0,0 +1,3 @@ +export const config = { + AUTH_URL: import.meta.env.VITE_AUTH_TOKEN_URL as string, +}; diff --git a/examples/auth-request-token/react/src/env.d.ts b/examples/auth-request-token/react/src/env.d.ts index 7421e8e743..1fc76da9eb 100644 --- a/examples/auth-request-token/react/src/env.d.ts +++ b/examples/auth-request-token/react/src/env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_ABLY_KEY: string; + readonly VITE_AUTH_TOKEN_URL: string; } interface ImportMeta { diff --git a/src/data/examples/index.ts b/src/data/examples/index.ts index 2705fb6ade..d05e7769f0 100644 --- a/src/data/examples/index.ts +++ b/src/data/examples/index.ts @@ -4,7 +4,6 @@ export const DEFAULT_EXAMPLE_LANGUAGES = ['javascript', 'react']; export const examples: Example[] = [ { -<<<<<<< HEAD id: 'ai-transport-message-per-token', name: 'Message per token streaming', description: 'Stream AI responses token-by-token using the message-per-token pattern.', @@ -31,8 +30,28 @@ export const examples: Example[] = [ products: ['auth'], layout: 'single-horizontal', visibleFiles: ['src/script.ts', 'App.tsx', 'Chat.tsx', 'Home.tsx', 'index.tsx'], - metaTitle: `Build chat history with Ably’s Chat SDK`, - metaDescription: `Learn how to build and use JWTs for client authentication with Ably. Secure token-based auth for realtime applications.`, + metaTitle: `Authenticate with Ably using JWTs`, + metaDescription: `Learn how to generate and use JWTs for client authentication with Ably. Secure token-based auth for realtime applications.`, + }, + { + id: 'auth-request-token', + name: 'Request Token', + description: 'Request an Ably Token for authenticating users.', + products: ['auth'], + layout: 'single-horizontal', + visibleFiles: ['src/script.ts', 'App.tsx', 'Chat.tsx', 'Home.tsx', 'index.tsx'], + metaTitle: `Authenticate with Ably Token`, + metaDescription: `Learn how to request and use Ably Tokens for client authentication. Secure token-based auth for realtime applications.`, + }, + { + id: 'auth-request-token', + name: 'Request Token', + description: 'Request an Ably Token for authenticating users.', + products: ['auth'], + layout: 'single-horizontal', + visibleFiles: ['src/script.ts', 'App.tsx', 'Chat.tsx', 'Home.tsx', 'index.tsx'], + metaTitle: `Authenticate with Ably Token`, + metaDescription: `Learn how to request and use Ably Tokens for client authentication. Secure token-based auth for realtime applications.`, }, { id: 'chat-presence', From 2fd278a1371312096cd0a09459065cbdb1d11eed Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Mon, 19 Jan 2026 16:49:16 +0000 Subject: [PATCH 3/3] examples: update auth examples to use authCallback instead of authUrl - auth-generate-jwt: Switch JavaScript and React clients to authCallback - auth-request-token: Switch JavaScript and React clients to authCallback - Fix ES module compatibility (__dirname) in both servers - Change dotenv to load .env instead of .env.local - Fix JWT response format (send as text, not JSON) --- examples/auth-generate-jwt/javascript/src/script.ts | 10 +++++++++- examples/auth-generate-jwt/react/src/App.tsx | 12 ++++++++++-- examples/auth-generate-jwt/server/src/server.ts | 7 +++---- examples/auth-request-token/javascript/src/script.ts | 10 +++++++++- examples/auth-request-token/react/src/App.tsx | 10 +++++++++- examples/auth-request-token/server/src/server.ts | 4 ++-- src/data/examples/index.ts | 10 ---------- 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/examples/auth-generate-jwt/javascript/src/script.ts b/examples/auth-generate-jwt/javascript/src/script.ts index 4b7b51edb1..1892abab12 100644 --- a/examples/auth-generate-jwt/javascript/src/script.ts +++ b/examples/auth-generate-jwt/javascript/src/script.ts @@ -15,7 +15,15 @@ function handleConnect() { messageOne.textContent = '✓'; const realtimeClient = new Ably.Realtime({ - authUrl: config.AUTH_URL || 'http://localhost:3001/generate-jwt', + authCallback: async (_tokenParams, callback) => { + try { + const response = await fetch(config.AUTH_URL || 'http://localhost:3001/generate-jwt'); + const token = await response.text(); + callback(null, token); + } catch (error) { + callback(error instanceof Error ? error.message : String(error), null); + } + }, }); const messageTwo = document.getElementById('message-2'); diff --git a/examples/auth-generate-jwt/react/src/App.tsx b/examples/auth-generate-jwt/react/src/App.tsx index 8ed3fe2664..9cdc65a71f 100644 --- a/examples/auth-generate-jwt/react/src/App.tsx +++ b/examples/auth-generate-jwt/react/src/App.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import * as Ably from 'ably'; import { AblyProvider, useConnectionStateListener } from 'ably/react'; import './styles/styles.css'; @@ -59,7 +59,15 @@ export default function App() { // Initialize Ably client with JWT auth const realtimeClient = new Ably.Realtime({ - authUrl: config.AUTH_URL || 'http://localhost:3001/generate-jwt', + authCallback: async (_tokenParams, callback) => { + try { + const response = await fetch(config.AUTH_URL || 'http://localhost:3001/generate-jwt'); + const token = await response.text(); + callback(null, token); + } catch (error) { + callback(error instanceof Error ? error.message : String(error), null); + } + }, }); // Update second message diff --git a/examples/auth-generate-jwt/server/src/server.ts b/examples/auth-generate-jwt/server/src/server.ts index c9ec3d265c..b40aade6ae 100644 --- a/examples/auth-generate-jwt/server/src/server.ts +++ b/examples/auth-generate-jwt/server/src/server.ts @@ -1,9 +1,11 @@ import express from 'express'; import path from 'path'; +import { fileURLToPath } from 'url'; import dotenv from 'dotenv'; import cors from 'cors'; import crypto from 'crypto'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); dotenv.config({ path: path.resolve(__dirname, '../../../.env.local') }); const app = express(); @@ -21,8 +23,6 @@ function base64urlEncode(str: string) { } app.get('/generate-jwt', async (_req, res) => { - console.log('1 - /generate-jwt endpoint called'); - const ablyApiKey = process.env.VITE_ABLY_KEY || ''; const [apiKeyName, apiKeySecret] = ablyApiKey.split(':'); try { @@ -51,8 +51,7 @@ app.get('/generate-jwt', async (_req, res) => { const signature = base64urlEncode(hmac.digest('base64')); const ablyJwt = base64Header + '.' + base64Claims + '.' + signature; - console.log('2 - JWT generated: ', ablyJwt); - res.json(ablyJwt); + res.type('application/jwt').send(ablyJwt); } catch (error) { res.status(500).json({ error: 'Failed to generate token' }); } diff --git a/examples/auth-request-token/javascript/src/script.ts b/examples/auth-request-token/javascript/src/script.ts index 03a0ce5658..a57e1f0b68 100644 --- a/examples/auth-request-token/javascript/src/script.ts +++ b/examples/auth-request-token/javascript/src/script.ts @@ -15,7 +15,15 @@ function handleConnect() { messageOne.textContent = '✓'; const realtimeClient = new Ably.Realtime({ - authUrl: config.AUTH_URL || 'http://localhost:3001/request-token', + authCallback: async (_tokenParams, callback) => { + try { + const response = await fetch(config.AUTH_URL || 'http://localhost:3001/request-token'); + const token = await response.text(); + callback(null, token); + } catch (error) { + callback(error instanceof Error ? error.message : String(error), null); + } + }, }); const messageTwo = document.getElementById('message-2'); diff --git a/examples/auth-request-token/react/src/App.tsx b/examples/auth-request-token/react/src/App.tsx index 489639569b..fdb2b383eb 100644 --- a/examples/auth-request-token/react/src/App.tsx +++ b/examples/auth-request-token/react/src/App.tsx @@ -59,7 +59,15 @@ export default function App() { // Initialize Ably client with token auth const realtimeClient = new Ably.Realtime({ - authUrl: config.AUTH_URL || 'http://localhost:3001/request-token', + authCallback: async (_tokenParams, callback) => { + try { + const response = await fetch(config.AUTH_URL || 'http://localhost:3001/request-token'); + const token = await response.text(); + callback(null, token); + } catch (error) { + callback(error instanceof Error ? error.message : String(error), null); + } + }, }); // Update second message diff --git a/examples/auth-request-token/server/src/server.ts b/examples/auth-request-token/server/src/server.ts index c065fbba41..304038f55c 100644 --- a/examples/auth-request-token/server/src/server.ts +++ b/examples/auth-request-token/server/src/server.ts @@ -1,9 +1,11 @@ import express from 'express'; import path from 'path'; +import { fileURLToPath } from 'url'; import dotenv from 'dotenv'; import cors from 'cors'; import Ably from 'ably'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); dotenv.config({ path: path.resolve(__dirname, '../../../.env.local') }); const app = express(); @@ -19,10 +21,8 @@ app.use( const ably = new Ably.Rest(process.env.VITE_ABLY_KEY || ''); app.get('/request-token', async (_req, res) => { - console.log('1 - /request-token endpoint called'); try { const tokenRequest = await ably.auth.requestToken({ clientId: 'example-client-id' }); - console.log('2 - Token generated:', tokenRequest); res.json(tokenRequest); } catch (error) { res.status(500).json({ error: 'Failed to generate token' }); diff --git a/src/data/examples/index.ts b/src/data/examples/index.ts index d05e7769f0..c1fc99c47e 100644 --- a/src/data/examples/index.ts +++ b/src/data/examples/index.ts @@ -43,16 +43,6 @@ export const examples: Example[] = [ metaTitle: `Authenticate with Ably Token`, metaDescription: `Learn how to request and use Ably Tokens for client authentication. Secure token-based auth for realtime applications.`, }, - { - id: 'auth-request-token', - name: 'Request Token', - description: 'Request an Ably Token for authenticating users.', - products: ['auth'], - layout: 'single-horizontal', - visibleFiles: ['src/script.ts', 'App.tsx', 'Chat.tsx', 'Home.tsx', 'index.tsx'], - metaTitle: `Authenticate with Ably Token`, - metaDescription: `Learn how to request and use Ably Tokens for client authentication. Secure token-based auth for realtime applications.`, - }, { id: 'chat-presence', name: 'Chat presence',