-
Notifications
You must be signed in to change notification settings - Fork 46
[FTF-468] Several docs changes to highlight token auth over API key auth, as well as more info on using JWTs #3085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
umair-ably
wants to merge
1
commit into
main
Choose a base branch
from
authChanges
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| --- | ||
| title: "JWT integration" | ||
| meta_description: "Integrate Ably with existing JWT authentication systems." | ||
| --- | ||
|
|
||
| If your application already uses JWT for authentication, you can embed Ably tokens within your existing JWT structure. This approach lets you maintain a single token for both your application and Ably authentication. | ||
|
|
||
| For general JWT authentication patterns, see [token authentication](/docs/auth/token#scenarios). | ||
|
|
||
| ## Embedded Ably Token <a id="embedded-token"/> | ||
|
|
||
| If you want a single JWT for everything, embed an Ably token in your existing JWT: | ||
|
|
||
| **Server:** | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| import Ably from 'ably'; | ||
| import jwt from 'jsonwebtoken'; | ||
|
|
||
| const ably = new Ably.Rest(process.env.ABLY_API_KEY); | ||
|
|
||
| app.post('/auth/login', async (req, res) => { | ||
| const user = await authenticateUser(req.body.email, req.body.password); | ||
| if (!user) { | ||
| return res.status(401).json({ error: 'Invalid credentials' }); | ||
| } | ||
|
|
||
| // Get Ably token | ||
| const ablyToken = await ably.auth.requestToken({ | ||
| clientId: user.id, | ||
| capability: getAblyCapabilities(user), | ||
| }); | ||
|
|
||
| // Create your app JWT with embedded Ably token | ||
| const appJwt = jwt.sign( | ||
| { | ||
| userId: user.id, | ||
| email: user.email, | ||
| // Embed Ably token | ||
| 'x-ably-token': ablyToken.token, | ||
| }, | ||
| process.env.APP_JWT_SECRET, | ||
| { expiresIn: '1h' } // Must not exceed Ably token expiry | ||
| ); | ||
|
|
||
| res.json({ token: appJwt }); | ||
| }); | ||
| ``` | ||
| </Code> | ||
|
|
||
| <Aside data-type='important'> | ||
| The outer JWT's expiry must not exceed the embedded Ably token's expiry. | ||
| </Aside> | ||
|
|
||
| ## Token refresh strategy <a id="refresh"/> | ||
|
|
||
| When using Ably JWTs, the Ably SDK automatically handles token refresh. Simply implement an `authCallback` that fetches a new token from your server: | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| const realtime = new Ably.Realtime({ | ||
| authCallback: async (tokenParams, callback) => { | ||
| try { | ||
| // This callback is automatically called when: | ||
| // 1. Initial connection | ||
| // 2. Token is about to expire | ||
| // 3. After a disconnection that requires re-auth | ||
| const response = await fetch('/api/ably-jwt', { | ||
| credentials: 'include', // Include cookies for session auth | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| // Handle auth errors - user may need to re-login | ||
| throw new Error('Authentication failed'); | ||
| } | ||
|
|
||
| const ablyJwt = await response.text(); | ||
| callback(null, ablyJwt); | ||
| } catch (error) { | ||
| callback(error, null); | ||
| } | ||
| }, | ||
| }); | ||
| ``` | ||
| </Code> | ||
|
|
||
| The Ably SDK automatically calls your `authCallback` before the current token expires, ensuring seamless token rotation. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| --- | ||
| title: Authentication quick reference | ||
| meta_description: "Choose the right authentication method for your use case with this quick reference guide." | ||
| --- | ||
|
|
||
| Use this guide for copy-paste ready code examples for common authentication patterns. | ||
|
|
||
| ## authUrl vs authCallback | ||
|
|
||
| Both options work on all platforms. Choose based on your needs: | ||
|
|
||
| - **`authUrl`**: Simpler setup. Provide a URL and the SDK handles the HTTP request. In browsers, cookies are sent automatically for same-origin requests. Use `authHeaders` to add custom headers like Bearer tokens. | ||
|
|
||
| - **`authCallback`**: Full control. You implement the token-fetching logic, which is useful when integrating with your app's existing HTTP client, native networking libraries, or platform-specific secure storage. | ||
|
|
||
| ## Quick examples by platform | ||
|
|
||
| ### Client-side (use token authentication) | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| // ALWAYS use token authentication in browsers | ||
| const realtime = new Ably.Realtime({ | ||
| authCallback: async (tokenParams, callback) => { | ||
| try { | ||
| const response = await fetch('/api/ably-token'); | ||
| const token = await response.text(); | ||
| callback(null, token); | ||
| } catch (error) { | ||
| callback(error, null); | ||
| } | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| ```swift | ||
| let options = ARTClientOptions() | ||
| options.authCallback = { params, callback in | ||
| fetchToken { result in | ||
| switch result { | ||
| case .success(let token): callback(token, nil) | ||
| case .failure(let error): callback(nil, error) | ||
| } | ||
| } | ||
| } | ||
| let realtime = ARTRealtime(options: options) | ||
| ``` | ||
|
|
||
| ```kotlin | ||
| val options = ClientOptions().apply { | ||
| authCallback = { params -> fetchToken() } | ||
| } | ||
| val realtime = AblyRealtime(options) | ||
| ``` | ||
| </Code> | ||
|
|
||
| ### Server-side (API key is safe) | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| // Server-side: Use REST client for token generation and API operations | ||
| import Ably from 'ably'; | ||
| const ably = new Ably.Rest(process.env.ABLY_API_KEY); | ||
| ``` | ||
|
|
||
| ```python | ||
| # Server-side: Use REST client for token generation and API operations | ||
| from ably import AblyRest | ||
| ably = AblyRest(os.environ['ABLY_API_KEY']) | ||
| ``` | ||
| </Code> | ||
|
|
||
| ## Common mistakes to avoid | ||
|
|
||
| ### Never do this in client-side code | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| // WRONG: Exposes your API key secret in browser | ||
| const realtime = new Ably.Realtime('app-id.key-id:secret'); | ||
|
|
||
| // WRONG: API key in client-side environment variable | ||
| // (bundlers include these in the client bundle) | ||
| const realtime = new Ably.Realtime(import.meta.env.VITE_ABLY_KEY); | ||
|
|
||
| // WRONG: Hardcoded key | ||
| const realtime = new Ably.Realtime({ | ||
| key: 'xxxxx.yyyyy:zzzzz', | ||
| clientId: 'user-123', | ||
| }); | ||
| ``` | ||
| </Code> | ||
|
|
||
| ### Do this instead | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| // CORRECT: Token authentication keeps your key safe | ||
| const realtime = new Ably.Realtime({ | ||
| authCallback: async (tokenParams, callback) => { | ||
| try { | ||
| const response = await fetch('/api/ably-token'); | ||
| const token = await response.text(); | ||
| callback(null, token); | ||
| } catch (error) { | ||
| callback(error, null); | ||
| } | ||
| }, | ||
| // clientId is assigned by your auth server, not here | ||
| }); | ||
| ``` | ||
| </Code> | ||
|
|
||
| ## Server auth endpoint template | ||
|
|
||
| Your server needs an endpoint that creates JWTs. No Ably SDK is required—use any JWT library: | ||
|
|
||
| <Code> | ||
| ```javascript | ||
| // Server-side: /api/ably-token endpoint | ||
| import jwt from 'jsonwebtoken'; | ||
|
|
||
| const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':'); | ||
|
|
||
| app.get('/api/ably-token', async (req, res) => { | ||
| // 1. Get authenticated user from your auth middleware | ||
| const userId = req.user?.id; | ||
| if (!userId) { | ||
| return res.status(401).json({ error: 'Not authenticated' }); | ||
| } | ||
|
|
||
| // 2. Create JWT with user's identity | ||
| const ablyJwt = jwt.sign( | ||
| { | ||
| 'x-ably-capability': JSON.stringify({ | ||
| '*': ['publish', 'subscribe', 'presence', 'history'], | ||
| }), | ||
| 'x-ably-clientId': userId, | ||
| }, | ||
| keySecret, | ||
| { | ||
| algorithm: 'HS256', | ||
| keyid: keyName, | ||
| expiresIn: '1h', | ||
| } | ||
| ); | ||
|
|
||
| // 3. Return to client | ||
| res.send(ablyJwt); | ||
| }); | ||
| ``` | ||
|
|
||
| ```python | ||
| # Server-side: /api/ably-token endpoint | ||
| from flask import Flask, g | ||
| import jwt | ||
| import os | ||
| import json | ||
| import time | ||
|
|
||
| app = Flask(__name__) | ||
| api_key = os.environ['ABLY_API_KEY'] | ||
| key_name, key_secret = api_key.split(':') | ||
|
|
||
| @app.route('/api/ably-token') | ||
| def get_ably_token(): | ||
| # 1. Get authenticated user from your auth middleware (e.g., g.user, current_user) | ||
| user_id = getattr(g, 'user', {}).get('id') | ||
| if not user_id: | ||
| return {'error': 'Not authenticated'}, 401 | ||
|
|
||
| # 2. Create JWT with user's identity | ||
| now = int(time.time()) | ||
| ably_jwt = jwt.encode( | ||
| { | ||
| 'iat': now, | ||
| 'exp': now + 3600, | ||
| 'x-ably-capability': json.dumps({ | ||
| '*': ['publish', 'subscribe', 'presence', 'history'] | ||
| }), | ||
| 'x-ably-clientId': user_id, | ||
| }, | ||
| key_secret, | ||
| algorithm='HS256', | ||
| headers={'kid': key_name} | ||
| ) | ||
|
|
||
| # 3. Return to client | ||
| return ably_jwt | ||
| ``` | ||
| </Code> | ||
|
|
||
| ## See also | ||
|
|
||
| - [Token authentication](/docs/auth/token) - Full token auth documentation | ||
| - [Capabilities](/docs/auth/capabilities) - Fine-grained permission control | ||
| - [Identified clients](/docs/auth/identified-clients) - Assigning trusted client identities |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's teh case, why are you not specifying a TTL when issuing the token?