The server-side flow worked fine, but I am having an issue with the client-side OAuth flow. Here is my client:
import { createClient } from '@osdk/client';
import { User, Users } from '@osdk/foundry.admin';
import { createPublicOauthClient } from '@osdk/oauth';
import { FoundryClient, Token } from '@codestrap/developer-foundations-types';
// this is a utility method to manage usage of the Foundry Client and ensure we only get a singleton
// files in the palantir services package can't use the container to get the foundry client, nor should they really
// They are in the same package
let client: FoundryClient | undefined = undefined;
export function getFoundryClient(): FoundryClient {
if (!client) {
client = createFoundryClient();
}
return client;
}
function createFoundryClient(): FoundryClient {
// log ENV vars
console.log('Environment variable keys:');
Object.keys(process.env).forEach((key) => {
if (key.indexOf('NEXT_PUBLIC_') >= 0) {
console.log(`- ${key}`);
}
});
if (!process.env['NEXT_PUBLIC_OSDK_CLIENT_ID']
|| !process.env['NEXT_PUBLIC_REDIRECT_URL']
|| !process.env['NEXT_PUBLIC_FOUNDRY_STACK_URL']
|| !process.env['NEXT_PUBLIC_ONTOLOGY_RID']
) {
throw new Error(
'missing required env vars: NEXT_PUBLIC_OSDK_CLIENT_ID, NEXT_PUBLIC_REDIRECT_URL, NEXT_PUBLIC_FOUNDRY_STACK_URL, NEXT_PUBLIC_ONTOLOGY_RID'
);
}
// setup the OSDK
const clientId: string = process.env['NEXT_PUBLIC_OSDK_CLIENT_ID']!;
const url: string = process.env['NEXT_PUBLIC_FOUNDRY_STACK_URL']!;
const ontologyRid: string = process.env['NEXT_PUBLIC_ONTOLOGY_RID']!;
const redirectUrl: string = process.env['NEXT_PUBLIC_REDIRECT_URL']!;
const scopes: string[] = [
'api:use-ontologies-read',
'api:use-ontologies-write',
'api:use-admin-read',
'api:use-connectivity-read',
'api:use-connectivity-execute',
'api:use-orchestration-read',
'api:use-mediasets-read',
'api:use-mediasets-write'
];
const auth = createPublicOauthClient(clientId, url, redirectUrl, true, undefined, window.location.toString(), scopes);
const client = createClient(url, ontologyRid, auth);
const getUser = async () => {
const user: User = await Users.getCurrent(client);
return user;
};
let token: Token | undefined;
let tokenExpire: Date | undefined;
let pendingRequest: Promise<Token> | undefined;
auth.addEventListener('signIn', (evt) => {
token = evt.detail; // Token
tokenExpire = new Date(token.expires_at);
});
auth.addEventListener('signOut', (evt) => {
token = undefined;
tokenExpire = undefined;
});
auth.addEventListener('refresh', (evt) => {
token = evt.detail; // Token
tokenExpire = new Date(token.expires_at);
});
const getToken = async function () {
if (token && tokenExpire) {
// add 60 seconds to account for processing time
const skew = tokenExpire.getTime() + 60000;
if (skew > new Date().getTime()) {
return token.access_token;
}
}
// avoid duplicate signin requests
if (!pendingRequest) {
pendingRequest = auth.signIn();
}
try {
token = await pendingRequest;
return token.access_token;
} catch (e) {
console.log(e);
throw (e);
} finally {
pendingRequest = undefined;
}
}
return { auth, ontologyRid, url, client, getUser, getToken };
}
I use the client in my React applications as follows:
const { client, getUser, getToken } = foundryClientFactory(SupportedFoundryClients.PUBLIC, undefined);
const userResource = createUserResource(client);
const tokenResource = createTokenResource(getToken);
//resource
// userResource.ts
/** Minimal resource wrapper for Suspense */
function createResource<T>(promise: Promise<T>) {
let status: "pending" | "success" | "error" = "pending";
let result: T;
let suspender = promise.then(
(r) => {
status = "success";
result = r;
},
(e) => {
status = "error";
result = e;
}
);
return {
read(): T {
if (status === "pending") throw suspender;
if (status === "error") throw result;
return result;
},
};
}
export function createTokenResource(getToken: () => Promise<string>) {
return createResource<string>(getToken());
}
I am able to trigger the OAuth flow to log in to Foundry and accept the permissions. However, on redirect, I get the following error:
curl 'https://<MY_STACK_URL>/multipass/api/oauth2/token' \
-H 'accept: application/json' \
-H 'accept-language: en-US,en;q=0.9' \
-H 'cache-control: no-cache' \
-H 'content-type: application/x-www-form-urlencoded;charset=UTF-8' \
-H 'origin: http://localhost:4200' \
-H 'pragma: no-cache' \
-H 'priority: u=1, i' \
-H 'referer: http://localhost:4200/' \
-H 'sec-ch-ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: cross-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36' \
--data-raw 'redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fvickie&code_verifier=<RETURNED_VERIFIER>&code=<RETURNED_CODE>&grant_type=authorization_code&client_id=<MY_CLIENT_ID>'
{"error":"invalid_request","error_description":"Missing credentials"}
I’m pretty sure I am handling the flow incorrectly. I thought maybe I should be checking `auth.getTokenOrUndefined()` to get the retrieved token, but it is always undefined. How can I handle the callback correctly and retrieve the token?