Hi John.
Nick.
Original Message:
Sent: 06-06-2025 14:22
From: John Carnell
Subject: Error : You are not authorized to perform the requested action
Hi Nick,
Agree that PCKE is a better solution. I answered it based on the current state of what was asked not what is the better solution :). PCKE is a better solution.
Thanks,
John
------------------------------
John Carnell
Director, Developer Engagement
Original Message:
Sent: 05-29-2025 17:09
From: Nick Tait
Subject: Error : You are not authorized to perform the requested action
Hi John.
This is very interesting. Thanks for taking the time to explain the mechanics of how to achieve this.
I wonder whether it would have been simpler to explain how to transition an Implicit Grant app to use PKCE Grant? Wouldn't that also solve the problem (because a refresh token would be issued) with a lot less code, and the app would also be more secure?
Thanks,
Nick.
------------------------------
Nick Tait
Genesys Consultant
Original Message:
Sent: 05-28-2025 13:49
From: John Carnell
Subject: Error : You are not authorized to perform the requested action
Hi Chandan,
It could be that your token is expiring. Unfortunately, the Genesys Cloud implicit grant flow does not support automatic token refreshes. The code authorization grant flow does. The Implicit Grant flow is designed for client-side applications (like your web-based app) and returns an access token directly in the URL fragment, but it does not provide a refresh token. Access tokens typically expire after 3600 seconds (1 hour) in Genesys Cloud.
To maintain a seamless user experience without requiring re-login, you can implement a strategy to proactively obtain a new access token before the current one expires. Below, I'll outline how to achieve this using the Genesys Cloud JavaScript SDK and best practices for handling token expiration.
Why Implicit Grant Doesn't Support Refresh Tokens
The OAuth Implicit Grant flow is intended for browser-based applications where the client (your web app) cannot securely store a client secret or refresh token. As a result, the flow prioritizes simplicity but sacrifices the ability to use refresh tokens. To "refresh" the token, you must re-initiate the OAuth flow, typically silently, to obtain a new access token.
Solution: Silent Authentication with Genesys Cloud SDK
You can use the Genesys Cloud JavaScript SDK (purecloud-platform-client-v2) to perform silent authentication, which allows your application to obtain a new access token without user interaction, provided the user's session with the Genesys Cloud identity provider (IdP) is still active. This is typically done by redirecting to the OAuth endpoint in a hidden iframe or using a session-based approach.
Here's how you can implement automatic token refresh:
1. Understand Token Expiration
Genesys Cloud access tokens have a default expiration of 3600 seconds (1 hour).
You need to track the token's expiration time and initiate a refresh before it expires (e.g., 5 minutes before expiration).
2. Use the Genesys Cloud JavaScript SDK
The SDK simplifies OAuth interactions. Ensure you have included the SDK in your project:
<script src="https://sdk-cdn.mypurecloud.com/javascript/2.0.0/purecloud-platform-client-v2.min.js"></script>
3. Initialize the SDK and Store Token Metadata
When the user authenticates via the Implicit Grant flow, the SDK will handle the token extraction from the URL fragment. Store the token and its expiration time for tracking.
const platformClient = require('purecloud-platform-client-v2/client');const clientId = 'YOUR_CLIENT_ID'; // Your OAuth client IDconst redirectUri = 'https://your-app.com/auth-callback'; // Your redirect URI// Initialize the clientconst client = platformClient.ApiClient.instance;client.setEnvironment('mypurecloud.com'); // Set your Genesys Cloud region// Authenticate (this happens on page load after redirect)client.loginImplicitGrant(clientId, redirectUri) .then(() => { // Token is now available in client.authData const accessToken = client.authData.accessToken; const tokenExpiry = Date.now() + (client.authData.expiresIn * 1000); // expiresIn is in seconds console.log('Access Token:', accessToken); console.log('Token Expiry:', new Date(tokenExpiry)); // Store token and expiry for refresh logic localStorage.setItem('accessToken', accessToken); localStorage.setItem('tokenExpiry', tokenExpiry); // Start the refresh timer scheduleTokenRefresh(tokenExpiry); }) .catch((err) => { console.error('Authentication failed:', err); });
4. Schedule Token Refresh
To avoid token expiration, schedule a refresh before the token expires. For example, refresh 5 minutes (300,000 ms) before expiration.
function scheduleTokenRefresh(expiryTimestamp) { const bufferTime = 300000; // 5 minutes in milliseconds const timeUntilExpiry = expiryTimestamp - Date.now() - bufferTime; if (timeUntilExpiry > 0) { setTimeout(() => { refreshAccessToken(); }, timeUntilExpiry); } else { // Token is already expired or about to expire; refresh immediately refreshAccessToken(); }}
5. Implement Silent Authentication for Refresh
To refresh the token silently, you can use the SDK's loginImplicitGrant method with a silent authentication flow. This typically involves redirecting to the Genesys Cloud OAuth endpoint in a hidden iframe.
function refreshAccessToken() { // Re-initiate Implicit Grant flow with prompt=none for silent authentication const authUrl = `https://login.mypurecloud.com/oauth/authorize?` + `client_id=${encodeURIComponent(clientId)}` + `&response_type=token` + `&redirect_uri=${encodeURIComponent(redirectUri)}` + `&prompt=none`; // Ensures no user interaction (if session is active) // Create a hidden iframe for silent authentication const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = authUrl; document.body.appendChild(iframe); // Listen for the redirect in the iframe window.addEventListener('message', (event) => { // Ensure the message is from the expected origin if (event.origin === 'https://your-app.com') { const url = event.data; if (url && url.includes('access_token')) { // Extract token from URL fragment const tokenMatch = url.match(/access_token=([^&]+)/); const expiresInMatch = url.match(/expires_in=([^&]+)/); if (tokenMatch && expiresInMatch) { const newAccessToken = tokenMatch[1]; const newExpiresIn = parseInt(expiresInMatch[1]) * 1000; // Convert to milliseconds const newTokenExpiry = Date.now() + newExpiresIn; // Update SDK with new token client.setAccessToken(newAccessToken); // Update stored token and expiry localStorage.setItem('accessToken', newAccessToken); localStorage.setItem('tokenExpiry', newTokenExpiry); console.log('Token refreshed successfully'); // Schedule the next refresh scheduleTokenRefresh(newTokenExpiry); } } // Clean up iframe document.body.removeChild(iframe); } });}
6. Configure the Auth Callback to Post Message
In your redirect URI page (auth-callback), ensure the URL fragment (containing the new token) is sent to the parent window (main app) for processing.
// In your auth-callback.html or equivalentwindow.onload = () => { if (window.location.hash) { // Post the URL with the new token to the parent window window.parent.postMessage(window.location.href, 'https://your-app.com'); }};
7. Handle Errors and Fallback
If silent authentication fails (e.g., the user's session with the IdP has expired), the prompt=none request will return an error. You should handle this by prompting the user to re-authenticate.
function refreshAccessToken() { const authUrl = `https://login.mypurecloud.com/oauth/authorize?` + `client_id=${encodeURIComponent(clientId)}` + `&response_type=token` + `&redirect_uri=${encodeURIComponent(redirectUri)}` + `&prompt=none`; const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = authUrl; document.body.appendChild(iframe); window.addEventListener('message', (event) => { if (event.origin === 'https://your-app.com') { const url = event.data; if (url.includes('error')) { console.error('Silent authentication failed:', url); // Fallback: Redirect user to full login client.loginImplicitGrant(clientId, redirectUri) .then(() => { console.log('User re-authenticated'); }) .catch((err) => { console.error('Re-authentication failed:', err); }); } else if (url.includes('access_token')) { // Handle successful token refresh as above // ... } document.body.removeChild(iframe); } });}
Key Considerations
Session Dependency: Silent authentication (prompt=none) relies on an active session with the Genesys Cloud IdP (e.g., SSO provider or Genesys Cloud login). If the session expires, silent authentication will fail, and you'll need to prompt the user to log in again.
Security: Ensure your application is served over HTTPS to protect tokens in transit. Avoid storing tokens in insecure locations (e.g., localStorage) if possible; consider sessionStorage or in-memory storage for better security.
CORS and Redirect URI: Ensure the redirectUri is correctly configured in your Genesys Cloud OAuth client settings and matches the URL of your app.
Token Expiry Buffer: Refresh tokens well before expiration (e.g., 5 minutes) to account for network delays or processing time.
Error Handling: Always handle cases where silent authentication fails (e.g., user session expired) and provide a fallback to explicit login.
Alternative: Switch to Authorization Code Grant
If your application can be modified to run server-side logic (e.g., with a backend), consider switching to the Authorization Code Grant flow. This flow provides a refresh token, which is more secure and simplifies token refresh. The backend would handle token storage and refresh, reducing the risk of token exposure in the browser.
To use Authorization Code Grant:
Configure a server-side endpoint to handle the OAuth callback.
Use the refresh token to obtain new access tokens via the /oauth/token endpoint.
Update your client-side app to fetch tokens from your backend securely.
Example Full Flow
Here's a simplified version of the complete JavaScript logic:
const platformClient = require('purecloud-platform-client-v2/client');const clientId = 'YOUR_CLIENT_ID';const redirectUri = 'https://your-app.com/auth-callback';const client = platformClient.ApiClient.instance;client.setEnvironment('mypurecloud.com');function scheduleTokenRefresh(expiryTimestamp) { const bufferTime = 300000; // 5 minutes const timeUntilExpiry = expiryTimestamp - Date.now() - bufferTime; if (timeUntilExpiry > 0) { setTimeout(refreshAccessToken, timeUntilExpiry); } else { refreshAccessToken(); }}function refreshAccessToken() { const authUrl = `https://login.mypurecloud.com/oauth/authorize?` + `client_id=${encodeURIComponent(clientId)}` + `&response_type=token` + `&redirect_uri=${encodeURIComponent(redirectUri)}` + `&prompt=none`; const iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = authUrl; document.body.appendChild(iframe); window.addEventListener('message', (event) => { if (event.origin === 'https://your-app.com') { const url = event.data; if (url.includes('access_token')) { const tokenMatch = url.match(/access_token=([^&]+)/); const expiresInMatch = url.match(/expires_in=([^&]+)/); if (tokenMatch && expiresInMatch) { const newAccessToken = tokenMatch[1]; const newExpiresIn = parseInt(expiresInMatch[1]) * 1000; const newTokenExpiry = Date.now() + newExpiresIn; client.setAccessToken(newAccessToken); localStorage.setItem('accessToken', newAccessToken); localStorage.setItem('tokenExpiry', newTokenExpiry); scheduleTokenRefresh(newTokenExpiry); } } else if (url.includes('error')) { console.error('Silent auth failed, redirecting to login'); client.loginImplicitGrant(clientId, redirectUri); } document.body.removeChild(iframe); } }, { once: true });}// Initial authenticationclient.loginImplicitGrant(clientId, redirectUri) .then(() => { const accessToken = client.authData.accessToken; const tokenExpiry = Date.now() + (client.authData.expiresIn * 1000); localStorage.setItem('accessToken', accessToken); localStorage.setItem('tokenExpiry', tokenExpiry); scheduleTokenRefresh(tokenExpiry); }) .catch((err) => console.error('Initial auth failed:', err));
Testing and Debugging
Test Silent Authentication: Ensure prompt=none works by testing with an active Genesys Cloud session. Open the browser's developer tools to monitor network requests and iframe behavior.
Handle Edge Cases: Test scenarios where the user's session has expired or the OAuth client is misconfigured.
Logging: Add logging to track token refresh success/failure for debugging.
Additional Resources
------------------------------
John Carnell
Director, Developer Engagement
Original Message:
Sent: 05-28-2025 11:38
From: Chandan Vishwakarma Ramachandra
Subject: Error : You are not authorized to perform the requested action
Hi John, we use Javascript platform SDK from genesys to make api calls. The sdk client is initialized using implicitGrant method : client.loginImplicitGrant()
------------------------------
Chandan Vishwakarma Ramachandra
na
Original Message:
Sent: 05-28-2025 09:03
From: John Carnell
Subject: Error : You are not authorized to perform the requested action
Hi Chandan,
Quick question are you using a OAuth client credential grant or some other type of OAuth grant.
Thanks,
John
------------------------------
John Carnell
Director, Developer Engagement
Original Message:
Sent: 05-22-2025 17:04
From: Chandan Vishwakarma Ramachandra
Subject: Error : You are not authorized to perform the requested action
We have a stand alone app where we make genesys api call using platform SDK javascript. This is a long running application, over 8 hours after each login. After certain duration, we are seeing "You are not authorized to perform the requested action" when making api calls. These errors are random and not specific to a user. Best guess is token expires and then we may be seeing this. Is there a way to refresh platform SDK session periodically like 15 mins once?
#PlatformSDK
------------------------------
Chandan Vishwakarma Ramachandra
na
------------------------------