diff --git a/README.md b/README.md index 21a81e22..940887fa 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,15 @@ const client = new okta.Client({ keyId: 'kidValue' }); ``` +```js +const client = new okta.Client({ + orgUrl: 'https://dev-1234.oktapreview.com/', + authorizationMode: 'ClientSecret', + clientId: '{oauth application ID}', + clientSecret: '{oauth application Secret}', + scopes: ['okta.users.manage'] +}); +``` The `privateKey` can be passed in the following ways: - As a JSON encoded string of a JWK object diff --git a/package.json b/package.json index a7c12f40..fd22aea3 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,10 @@ "jest": "JEST_JUNIT_OUTPUT_DIR=./test-reports jest --coverage --ci --testResultsProcessor=jest-junit test/jest/*.js", "predocs": "rimraf ./jsdocs && mkdir jsdocs/ && ./utils/make-jsdoc-readme.js > ./jsdocs/jsdoc-temp.md", "docs": "./node_modules/.bin/jsdoc src/ -c ./docs/config.json -d ./jsdocs/ -P ./package.json -R ./jsdocs/jsdoc-temp.md -r", - "test:integration": "yarn test:integration:oauth && yarn test:integration:ssws", + "test:integration": "yarn test:integration:oauth && yarn test:integration:ssws && yarn test:integration:clientsecret", "test:integration:ssws": "TEST_TYPE=it OKTA_CLIENT_AUTHORIZATIONMODE=SSWS mocha test/it/*.ts", "test:integration:oauth": "TEST_TYPE=it OKTA_CLIENT_AUTHORIZATIONMODE=PrivateKey mocha test/it/user-get.ts", + "test:integration:clientsecret": "TEST_TYPE=it OKTA_CLIENT_AUTHORIZATIONMODE=ClientSecret mocha test/it/user-get.ts", "test:unit": "TEST_TYPE=unit mocha test/unit/*.js", "test:types": "tsd && tsc --noEmit --isolatedModules --importsNotUsedAsValues error src/types/**/*.ts", "test": "yarn eslint && yarn test:types && yarn test:unit && yarn test:integration && yarn jest", diff --git a/src/client.js b/src/client.js index dfcbad94..d41596d1 100644 --- a/src/client.js +++ b/src/client.js @@ -81,7 +81,7 @@ class Client { errors.push('Okta Org URL not provided'); } - if (!parsedConfig.client.token && parsedConfig.client.authorizationMode !== 'PrivateKey') { + if (!parsedConfig.client.token && parsedConfig.client.authorizationMode === 'SSWS') { errors.push('Okta API token not provided'); } @@ -95,6 +95,16 @@ class Client { if (!parsedConfig.client.privateKey) { errors.push('Private Key not provided'); } + } else if (parsedConfig.client.authorizationMode === 'ClientSecret') { + if (!parsedConfig.client.clientId) { + errors.push('Okta Client ID not provided'); + } + if (!parsedConfig.client.scopes) { + errors.push('Scopes not provided'); + } + if (!parsedConfig.client.clientSecret) { + errors.push('Okta Client Secret not provided'); + } } else if (parsedConfig.client.authorizationMode !== 'SSWS') { errors.push('Unknown Authorization Mode'); } @@ -111,6 +121,11 @@ class Client { this.privateKey = parsedConfig.client.privateKey; this.keyId = parsedConfig.client.keyId; this.oauth = new OAuth(this); + } else if (this.authorizationMode === 'ClientSecret') { + this.clientId = parsedConfig.client.clientId; + this.scopes = parsedConfig.client.scopes.split(' '); + this.clientSecret = parsedConfig.client.clientSecret; + this.oauth = new OAuth(this); } this.http = new Http({ diff --git a/src/config-loader.js b/src/config-loader.js index fab515da..a7bf5300 100644 --- a/src/config-loader.js +++ b/src/config-loader.js @@ -28,6 +28,7 @@ class ConfigLoader { orgUrl: '', token: '', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', diff --git a/src/oauth.js b/src/oauth.js index 31ba6d9d..4d47ce34 100644 --- a/src/oauth.js +++ b/src/oauth.js @@ -13,6 +13,7 @@ const { makeJwt } = require('./jwt'); const Http = require('./http'); +const { base64urlEncode } = require('njwt'); function formatParams(obj) { var str = []; @@ -44,6 +45,33 @@ class OAuth { return Promise.resolve(this.accessToken); } + if (this.client.authorizationMode === 'ClientSecret') { + const base64Creds = base64urlEncode(`${this.client.clientId}:${this.client.clientSecret}`); + const params = formatParams({ + grant_type: 'client_credentials', + scope: this.client.scopes.join(' ') + }); + return this.client.requestExecutor.fetch({ + url: `${this.client.baseUrl}/oauth2/default/v1/token`, + method: 'POST', + body: params, + headers: { + Accept: 'application/json', + Authorization: `Basic ${base64Creds}`, + 'cache-control': 'no-cache', + 'content-type': 'application/x-www-form-urlencoded' + } + }) + .then(Http.errorFilter) + .then(res => { + return res.json(); + }) + .then(accessToken => { + this.accessToken = accessToken; + return this.accessToken; + }); + } + const endpoint = '/oauth2/v1/token'; return this.getJwt(endpoint) .then(jwt => { diff --git a/src/types/client.d.ts b/src/types/client.d.ts index 1200575e..abdf55f2 100644 --- a/src/types/client.d.ts +++ b/src/types/client.d.ts @@ -58,6 +58,7 @@ export declare class Client { baseUrl: string; apiToken: string; clientId: string; + clientSecret: string; scopes: string[]; privateKey: string; keyId: string; diff --git a/src/types/configuration.d.ts b/src/types/configuration.d.ts index 2fa3cbf5..0ce186e0 100644 --- a/src/types/configuration.d.ts +++ b/src/types/configuration.d.ts @@ -19,6 +19,7 @@ export declare interface V2Configuration { orgUrl?: string, token?: string, clientId?: string, + clientSecret?: string, scopes?: string[], requestExecutor?: RequestExecutor, authorizationMode?: string, diff --git a/test/it/user-get.ts b/test/it/user-get.ts index a7104452..83f57db8 100644 --- a/test/it/user-get.ts +++ b/test/it/user-get.ts @@ -12,6 +12,8 @@ const client = new Client({ scopes: ['okta.users.manage'], orgUrl: orgUrl, token: process.env.OKTA_CLIENT_TOKEN, + clientId: process.env.OKTA_CLIENT_ID, + clientSecret: process.env.OKTA_CLIENT_SECRET, requestExecutor: new DefaultRequestExecutor() }); diff --git a/test/unit/config-loader.js b/test/unit/config-loader.js index 1108d38d..077d4a1e 100644 --- a/test/unit/config-loader.js +++ b/test/unit/config-loader.js @@ -22,6 +22,7 @@ describe('ConfigLoader', () => { orgUrl: '', token: '', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', @@ -52,6 +53,7 @@ describe('ConfigLoader', () => { orgUrl: '', token: '', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', @@ -77,6 +79,7 @@ describe('ConfigLoader', () => { token: '', authorizationMode: 'PrivateKey', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', @@ -101,6 +104,7 @@ describe('ConfigLoader', () => { authorizationMode: 'SSWS', token: '', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', @@ -132,6 +136,7 @@ describe('ConfigLoader', () => { token: '', authorizationMode: '', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', @@ -149,6 +154,7 @@ describe('ConfigLoader', () => { token: 'a', authorizationMode: 'SSWS', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '', @@ -166,6 +172,7 @@ describe('ConfigLoader', () => { token: 'a', authorizationMode: 'PrivateKey', clientId: '', + clientSecret: '', scopes: '', privateKey: '', keyId: '',