Skip to content

Commit

Permalink
refactor(core): clean up Client constructor and deprecate getToken (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
BugGambit authored Sep 18, 2024
1 parent 31699cc commit ba561d7
Show file tree
Hide file tree
Showing 24 changed files with 171 additions and 116 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Before you begin, ensure that you have the following:
### Setting up OIDC Authentication

- Initialize the SDK: Import and initialize the SDK in your application, providing the necessary configuration options such as the client ID, client secret, and redirect URI obtained from your OIDC IdP.
- Pass your authentication method to `getToken` property of SDK.
- Setup a token provider using the `oidcTokenProvider` property of SDK. Here you can provide a valid access token for the CDF API.

For code example you can check [quickstart.ts](https://github.com/cognitedata/cognite-sdk-js/blob/master/samples/nodejs/oidc-typescript/quickstart.ts#L1)

Expand Down
37 changes: 19 additions & 18 deletions guides/authentication.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Authentication in browsers

- [Use access tokens instead of API keys](#use-access-tokens-instead-of-api-keys)
- [Accessing different clusters](#accessing-different-clusters)
- [How to authenticate with the SDK?](#how-to-authenticate-with-the-sdk)
- [OpenID Connect (OIDC)](#openid-connect-oidc)
- [OIDC authentication using code authorization w pkce.](#oidc-authentication-using-code-authorization-w-pkce)
- [OIDC authentication using client credentials](#oidc-authentication-using-client-credentials)
- [Manually trigger authentication](#manually-trigger-authentication)
- [Cache access tokens](#cache-access-tokens)
- [Authentication in browsers](#authentication-in-browsers)
- [Use access tokens instead of API keys](#use-access-tokens-instead-of-api-keys)
- [Accessing different clusters](#accessing-different-clusters)
- [How to authenticate with the SDK?](#how-to-authenticate-with-the-sdk)
- [OpenID Connect (OIDC)](#openid-connect-oidc)
- [OIDC authentication using code authorization w pkce](#oidc-authentication-using-code-authorization-w-pkce)
- [OIDC authentication using client credentials](#oidc-authentication-using-client-credentials)
- [Example](#example)
- [Manually trigger authentication](#manually-trigger-authentication)
- [Cache access tokens](#cache-access-tokens)
- [More](#more)

## Use access tokens instead of API keys

Expand All @@ -28,18 +31,16 @@ const client = new CogniteClient({
appId: 'sample-app',
baseUrl: 'https://bluefield.cognitedata.com',
project: 'demo-project',
getToken: ...
oidcTokenProvider: ...
});
```

## How to authenticate with the SDK?

Quickly summarized, the application passes a `getToken` callback to the SDK and uses it to
get and renew tokens as needed. **Note** that the SDK comes with an implementation of the Cognite legacy
authentication flow, **but it does not come with an implementation for all IDPs**. If you need to
integrate your application and the SDK with, for example, Azure Active Directory, use the appropriate library
Quickly summarized, the application passes a `oidcTokenProvider` callback to the SDK and uses it to
get and renew tokens as needed. If you need to integrate your application and the SDK with, for example, Azure Active Directory, use the appropriate library
from Microsoft. If you need to integrate with Auth0, use their libraries and integrate with the SDK
via `getToken`.
via `oidcTokenProvider`.

## OpenID Connect (OIDC)

Expand Down Expand Up @@ -73,7 +74,7 @@ const configuration: Configuration = {
};

const pca = new PublicClientApplication(configuration);
const getToken = async () => {
const oidcTokenProvider = async () => {
const accountId = sessionStorage.getItem("account");
const account = pca.getAccountByLocalId(accountId)!;
const token = await pca.acquireTokenSilent({
Expand All @@ -92,7 +93,7 @@ const getToken = async () => {
const client = new CogniteClient({
project: "my-project",
appId: "demo-sample",
getToken
oidcTokenProvider
});

```
Expand Down Expand Up @@ -126,7 +127,7 @@ async function quickstart() {
appId: 'Cognite SDK samples',
project,
baseUrl: "https://api.cognitedata.com",
getToken: () =>
oidcTokenProvider: () =>
pca
.acquireTokenByClientCredential({
scopes: ['https://api.cognitedata.com/.default'],
Expand Down Expand Up @@ -176,7 +177,7 @@ If the token is invalid or timed out, the SDK triggers a standard auth-flow on t
```js
const client = new CogniteClient({
project: 'YOUR PROJECT NAME HERE',
getToken: () => Promise.resolve('ACCESS TOKEN FOR THE PROJECT HERE'),
oidcTokenProvider: () => Promise.resolve('ACCESS TOKEN FOR THE PROJECT HERE'),
});
```
Expand Down
2 changes: 1 addition & 1 deletion packages/alpha/src/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function setupLoggedInClient() {
appId: 'JS SDK integration tests (alpha)',
baseUrl: process.env.COGNITE_BASE_URL,
project: process.env.COGNITE_PROJECT as string,
getToken: () =>
oidcTokenProvider: () =>
login().then((account) => {
return account.access_token;
}),
Expand Down
6 changes: 3 additions & 3 deletions packages/beta/src/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function setupClient(baseUrl: string = Constants.BASE_URL) {
appId: 'JS SDK integration tests (beta)',
baseUrl: process.env.COGNITE_BASE_URL || baseUrl,
project: process.env.COGNITE_PROJECT || (project as string),
getToken: () =>
oidcTokenProvider: () =>
login().then((account) => {
return account.access_token;
}),
Expand All @@ -30,7 +30,7 @@ export function setupLoggedInClient() {
appId: 'JS SDK integration tests (beta)',
baseUrl: process.env.COGNITE_BASE_URL,
project: process.env.COGNITE_PROJECT as string,
getToken: () =>
oidcTokenProvider: () =>
login().then((account) => {
return account.access_token;
}),
Expand All @@ -52,7 +52,7 @@ export function setupLoggedInClientForUnitTest(
appId: 'JS SDK integration tests (beta)',
baseUrl: baseUrl || process.env.COGNITE_BASE_URL,
project: process.env.COGNITE_PROJECT as string,
getToken: () =>
oidcTokenProvider: () =>
login().then((account) => {
return account.access_token;
}),
Expand Down
74 changes: 51 additions & 23 deletions packages/core/src/__tests__/cogniteClient.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function setupClient(baseUrl: string = BASE_URL) {
appId: 'JS SDK integration tests',
project: 'test-project',
baseUrl,
getToken: () => Promise.resolve(accessToken),
oidcTokenProvider: () => Promise.resolve(accessToken),
});
}

Expand Down Expand Up @@ -74,7 +74,7 @@ describe('CogniteClient', () => {
appId: 'unit-test',
project: 'unit-test',
baseUrl: mockBaseUrl,
getToken: vi.fn(async () => 'test-token'),
oidcTokenProvider: vi.fn(async () => 'test-token'),
retryValidator: createUniversalRetryValidator(1),
});
await expect(client.get('/')).rejects.toThrowErrorMatchingInlineSnapshot(
Expand All @@ -83,8 +83,32 @@ describe('CogniteClient', () => {
expect(scope.isDone()).toBeTruthy();
});

describe('getToken', () => {
test('call getToken on 401', async () => {
describe('oidcTokenProvider', () => {
test('call oidcTokenProvider on 401', async () => {
nock(mockBaseUrl)
.get('/test')
.once()
.reply(401, { error: { message: 'unauthorized' } })
.get('/test')
.reply(200, { body: 'request ok' });

const oidcTokenProvider = vi.fn().mockResolvedValue('test-token');

const client = new BaseCogniteClient({
project,
appId: 'unit-test',
baseUrl: mockBaseUrl,
oidcTokenProvider,
});

const result = await client.get('/test');

expect(result.status).toEqual(200);
expect(result.data).toEqual({ body: 'request ok' });
expect(oidcTokenProvider).toHaveBeenCalledTimes(1);
});

test('ensure deprecated getToken still works', async () => {
nock(mockBaseUrl)
.get('/test')
.once()
Expand All @@ -108,16 +132,18 @@ describe('CogniteClient', () => {
expect(getToken).toHaveBeenCalledTimes(1);
});

test('getToken rejection should reject sdk requests', async () => {
const getToken = vi.fn().mockRejectedValue(new Error('auth error'));
test('oidcTokenProvider rejection should reject sdk requests', async () => {
const oidcTokenProvider = vi
.fn()
.mockRejectedValue(new Error('auth error'));

nock(mockBaseUrl).get('/test').reply(401, {});

const client = new BaseCogniteClient({
project,
appId: 'unit-test',
baseUrl: mockBaseUrl,
getToken,
oidcTokenProvider,
});

await expect(
Expand All @@ -126,14 +152,14 @@ describe('CogniteClient', () => {
'[Error: Request failed | status code: 401]'
);

expect(getToken).toHaveBeenCalledTimes(1);
expect(oidcTokenProvider).toHaveBeenCalledTimes(1);
});

test('getToken should be called once for parallel 401s', async () => {
test('oidcTokenProvider should be called once for parallel 401s', async () => {
nock(mockBaseUrl).get('/test').thrice().reply(401);
nock(mockBaseUrl).get('/test').thrice().reply(200);

const mockGetToken = vi.fn(async () => {
const oidcTokenProvider = vi.fn(async () => {
await sleepPromise(100);
return 'test-token';
});
Expand All @@ -142,7 +168,7 @@ describe('CogniteClient', () => {
project,
appId: 'unit-test',
baseUrl: mockBaseUrl,
getToken: mockGetToken,
oidcTokenProvider,
});

await Promise.all([
Expand All @@ -151,16 +177,16 @@ describe('CogniteClient', () => {
client.get('/test'),
]);

expect(mockGetToken).toBeCalledTimes(1);
expect(oidcTokenProvider).toBeCalledTimes(1);
});

test('getToken should be called once for parallel 401s with different response times', async () => {
test('oidcTokenProvider should be called once for parallel 401s with different response times', async () => {
nock(mockBaseUrl).get('/test').twice().reply(401);
nock(mockBaseUrl).get('/test-with-delay').delay(200).reply(401);
nock(mockBaseUrl).get('/test').twice().reply(200);
nock(mockBaseUrl).get('/test-with-delay').reply(200);

const mockGetToken = vi.fn(async () => {
const oidcTokenProvider = vi.fn(async () => {
await sleepPromise(100);
return 'test-token';
});
Expand All @@ -169,7 +195,7 @@ describe('CogniteClient', () => {
project,
appId: 'unit-test',
baseUrl: mockBaseUrl,
getToken: mockGetToken,
oidcTokenProvider,
});

await Promise.all([
Expand All @@ -178,17 +204,17 @@ describe('CogniteClient', () => {
client.get('/test-with-delay'),
]);

expect(mockGetToken).toBeCalledTimes(1);
expect(oidcTokenProvider).toBeCalledTimes(1);
});

test('getToken should be called more than once for sequential 401s', async () => {
test('oidcTokenProvider should be called more than once for sequential 401s', async () => {
nock(mockBaseUrl).get('/test').thrice().reply(401);
nock(mockBaseUrl).get('/test').thrice().reply(200);
nock(mockBaseUrl).get('/test').reply(401);
nock(mockBaseUrl).get('/test').reply(200);

let tokenCount = 0;
const mockGetToken = vi.fn(async () => {
const oidcTokenProvider = vi.fn(async () => {
await sleepPromise(100);
return `test-token${tokenCount++}`;
});
Expand All @@ -197,7 +223,7 @@ describe('CogniteClient', () => {
project,
appId: 'unit-test',
baseUrl: mockBaseUrl,
getToken: mockGetToken,
oidcTokenProvider,
});

await Promise.all([
Expand All @@ -208,7 +234,7 @@ describe('CogniteClient', () => {

await client.get('/test');

expect(mockGetToken).toBeCalledTimes(2);
expect(oidcTokenProvider).toBeCalledTimes(2);
});

test('new token should be used on retry for 401s', async () => {
Expand Down Expand Up @@ -238,19 +264,21 @@ describe('CogniteClient', () => {
});

let tokenCount = 0;
const mockGetToken = vi.fn(async () => `test-token${tokenCount++}`);
const oidcTokenProvider = vi.fn(
async () => `test-token${tokenCount++}`
);

const client = new BaseCogniteClient({
project,
appId: 'unit-test',
baseUrl: mockBaseUrl,
getToken: mockGetToken,
oidcTokenProvider,
});

await client.authenticate();
await client.get('/test');

expect(mockGetToken).toBeCalledTimes(3);
expect(oidcTokenProvider).toBeCalledTimes(3);
});
});
});
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export function setupClient(baseUrl: string = BASE_URL) {
return new BaseCogniteClient({
appId: 'JS SDK integration tests',
project: process.env.COGNITE_PROJECT as string,
getToken: () => Promise.resolve(process.env.COGNITE_CREDENTIALS as string),
oidcTokenProvider: () =>
Promise.resolve(process.env.COGNITE_CREDENTIALS as string),
baseUrl,
});
}
Expand Down
Loading

0 comments on commit ba561d7

Please sign in to comment.