We are setting up a new OpenAPI Resource to access a service we've developed using FastAPI, deployed to AWS Lambda. Authentication is provided by Auth0 + Google SSO, and we are retrieving valid access, refresh, and ID tokens without issue.
However, every call to our API via this Retool resource using this access token is taking roughly 50s to complete, even our verification endpoint (which accepts no input payload and produces a minimal response w/o accessing any additional resource) which resolves in a few milliseconds outside Retool. This can be seen in the screenshot below:
Analyzing our AWS logs for these calls confirms that 1. the API calls on our service are being resolved in an expected amount of time (<1s), and 2. the endpoints are being called by Retool at the very end of that ~50s timeline. The following screenshot shows the AWS logs for the "Check authorization status" logs shown in the previous screenshot. Note that the entire request takes less than a second, and that it is received at the very end 2:49:35 (PST), which was the same second the authentication check ended:
When the calls do complete on Retool's side, they are indeed successful and produce the desired outcome. But they're all taking far too long to resolve.
While monitoring the browser dev tools, the "Check authorization status" is happening completely server-side, in Retool's service, so it's impossible to determine what is consuming the remainder of the request time:
To test that this isn't inherently slow, I wrote a simple test client using NodeJS that tests the following:
- An OAuth2 handshake against Auth0, including Google SSO login
- Verification of the resulting access token against our validation endpoint
- Download and process of the OpenAPI spec from our server using SwaggerClient
This local script completes (modulo the actual process of signing in w/Google) in under 2s.
require('dotenv').config();
const express = require('express');
const axios = require('axios');
const SwaggerClient = require('swagger-client');
const querystring = require('querystring');
const { stat } = require('fs');
const app = express();
const PORT = 3000;
// OAuth URLs & Credentials from .env
const {
CLIENT_ID,
CLIENT_SECRET,
AUTHORIZATION_URL,
TOKEN_URL,
REDIRECT_URI,
OPENAPI_SPEC_URL,
VERIFICATION_URL,
} = process.env;
console.log("OAuth URLs & Credentials:", {
CLIENT_ID,
CLIENT_SECRET,
AUTHORIZATION_URL,
TOKEN_URL,
REDIRECT_URI,
OPENAPI_SPEC_URL,
VERIFICATION_URL,
});
// Step 1: Redirect user to OAuth 2.0 provider for authorization
app.get('/login', (req, res) => {
// Get the current timestamp as the state in seconds as the state
const state = Date.now();
const authUrl = `${AUTHORIZATION_URL}?` + querystring.stringify({
response_type: 'code',
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: 'openid profile email offline_access',
state: state,
});
res.redirect(authUrl);
});
// Step 2: Handle OAuth 2.0 Callback and Exchange Code for Tokens
app.get('/auth/callback', async (req, res) => {
const callbackStart = Date.now();
const { code } = req.query;
if (!code) {
return res.status(400).send("Authorization code missing.");
}
try {
// Exchange code for tokens
console.log("Exchanging code for tokens...", code);
const tokenResponse = await axios.post(TOKEN_URL, querystring.stringify({
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
}), {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
const timeToGetToken = Date.now() - callbackStart;
console.log("Time to get token:", timeToGetToken);
// Check if the response was successful
console.log("Token Response:", tokenResponse.data);
if (tokenResponse.status !== 200) {
throw new Error("Failed to exchange code for tokens.");
}
const {
access_token: accessToken,
refresh_token: refreshToken,
id_token: idToken,
state,
} = tokenResponse.data;
console.log("Access Token:", accessToken);
console.log("Refresh Token:", refreshToken);
console.log("ID Token:", idToken);
console.log("State:", state);
// Step 3: Fetch OpenAPI Spec using swagger-client
const client = await SwaggerClient({
url: OPENAPI_SPEC_URL
});
// Extract available endpoints
const endpoints = Object.keys(client.spec.paths);
const timeToParseSpec = Date.now() - callbackStart - timeToGetToken;
console.log("Time to parse spec:", timeToParseSpec);
// GET the verification endpoint with the bearer token to verify the token
const verificationResponse = await axios.get(VERIFICATION_URL, {
headers: { Authorization: `Bearer ${accessToken}` }
});
const verification = verificationResponse.data
const timeToVerifyToken = Date.now() - callbackStart - timeToGetToken - timeToParseSpec;
console.log("Time to verify token:", timeToVerifyToken);
// Log the verification response JSON
console.log("Verification Response:", verification);
// Elapsed time
const elapsedTime = Date.now() - callbackStart;
console.log("Elapsed Time:", elapsedTime);
// Display tokens and API endpoints
res.json({ accessToken, refreshToken, idToken, endpoints, verification });
} catch (error) {
console.error("Error:", error.response ? error.response.data : error.message);
res.status(500).send("Authentication failed.");
}
});
// Start Server
app.listen(PORT, () => {
console.log(`OAuth client running at http://localhost:${PORT}`);
console.log(`Login at: http://localhost:${PORT}/login`);
});
I'd like to understand why this resource is taking so long to complete every call, if there's a configuration I've gotten wrong, and if there's something that can be done to make this closer to the performance of API calls against this service outside of a Retool context.