Skip to content

Commit

Permalink
feat: Adds support for Node AuthN SDK (#184)
Browse files Browse the repository at this point in the history
Make AuthN part of SDK isomorphic so that can be used as Node SDK.
  • Loading branch information
manueltanzi-okta committed Jan 23, 2019
1 parent 97825e5 commit c31638b
Show file tree
Hide file tree
Showing 32 changed files with 568 additions and 185 deletions.
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.3.0

### Features

- [#184](https://github.com/okta/okta-auth-js/pull/184) - Adds support for calling the AuthN API from Node

## 2.2.0

### Bug Fixes
Expand Down Expand Up @@ -47,11 +53,11 @@
* `idToken.refresh`
* `idToken.decode`

## Features
### Features

* Clears whitespace around URLs when instantiating the client.
* Infer the `url` from the `issuer` to simplify client setup.

## Other
### Other

* Renames all `refresh` methods on the `token` and `tokenManager` objects to `renew`.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* [Configuration reference](#configuration-reference)
* [API Reference](#api-reference)
* [Building the SDK](#building-the-sdk)
* [Node JS Usage](#node-js-usage)
* [Contributing](#contributing)

The Okta Auth JavaScript SDK builds on top of our [Authentication API](https://developer.okta.com/docs/api/resources/authn) and [OAuth 2.0 API](https://developer.okta.com/docs/api/resources/oidc) to enable you to create a fully branded sign-in experience using JavaScript.
Expand Down Expand Up @@ -1614,6 +1615,54 @@ authClient.tokenManager.off('renewed');
authClient.tokenManager.off('renewed', myRenewedCallback);
```
## Node JS Usage
You can use this library on server side in your Node application as an Authentication SDK. It can only be used in this way for communicating with the [Authentication API](https://developer.okta.com/docs/api/resources/authn), **not** to implement an OIDC flow.
To include this library in your project, you can follow the instructions in the [Getting started](#getting-started) section.
### Configuration
You only need to set the `url` for your Okta Domain:
```javascript
var OktaAuth = require('@okta/okta-auth-js');

var config = {
// The URL for your Okta organization
url: 'https://{yourOktaDomain}'
};

var authClient = new OktaAuth(config);
```
### Supported APIs
Since the Node library can be used only for the Authentication flow, it implements only a subset of okta-auth-js APIs:
* [signIn](#signinoptions)
* [forgotPassword](#forgotpasswordoptions)
* [unlockAccount](#unlockaccountoptions)
* [verifyRecoveryToken](#verifyrecoverytokenoptions)
* [tx.resume](#txresume)
* [tx.exists](#txexists)
* [transaction.status](#transactionstatus)
* [LOCKED_OUT](#locked_out)
* [PASSWORD_EXPIRED](#password_expired)
* [PASSWORD_RESET](#password_reset)
* [PASSWORD_WARN](#password_warn)
* [RECOVERY](#recovery)
* [RECOVERY_CHALLENGE](#recovery_challenge)
* [MFA_ENROLL](#mfa_enroll)
* [MFA_ENROLL_ACTIVATE](#mfa_enroll_activate)
* [MFA_REQUIRED](#mfa_required)
* [MFA_CHALLENGE](#mfa_challenge)
* [SUCCESS](#success)
The main difference is that the Node library does **not** have a `session.setCookieAndRedirect` function, so you will have to redirect by yourself (for example using `res.redirect('https://www.yoursuccesspage.com')`).
The `SUCCESS` transaction will still include a `sessionToken` which you can use with the session APIs: https://github.com/okta/okta-sdk-nodejs#sessions.
## Building the SDK
In most cases, you won't need to build the SDK from source. If you want to build it yourself, you'll need to follow these steps:
Expand Down
46 changes: 46 additions & 0 deletions fetch/fetchRequest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*!
* Copyright (c) 2018-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

var fetch = require('cross-fetch');

function fetchRequest(method, url, args) {
var fetchPromise = fetch(url, {
method: method,
headers: args.headers,
body: JSON.stringify(args.data),
credentials: 'include'
})
.then(function(response) {
var error = !response.ok;
var status = response.status;
var respHandler = function(resp) {
var result = {
responseText: resp,
status: status
};
if (error) {
// Throwing response object since error handling is done in http.js
throw result;
}
return result;
};
if (response.headers.get('Accept') &&
response.headers.get('Accept').toLowerCase().indexOf('application/json') >= 0) {
return response.json().then(respHandler);
} else {
return response.text().then(respHandler);
}
});
return fetchPromise;
}

module.exports = fetchRequest;
17 changes: 17 additions & 0 deletions fetch/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*!
* Copyright (c) 2018-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*
*/

var fetchRequest = require('./fetchRequest');
var storageUtil = require('../lib/server/serverStorage');

module.exports = require('../lib/server/server')(storageUtil, fetchRequest);
18 changes: 18 additions & 0 deletions jest.browser.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"browser": true,
"coverageDirectory": "./build2/reports/coverage",
"restoreMocks": true,
"moduleNameMapper": {
"^OktaAuth(.*)$": "<rootDir>/jquery/$1"
},
"testMatch": [
"**/test/spec/*.js"
],
"testPathIgnorePatterns": [
"./test/spec/serverStorage.js"
],
"reporters": [
"default",
"jest-junit"
]
}
17 changes: 17 additions & 0 deletions jest.server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"coverageDirectory": "./build2/reports/coverage",
"restoreMocks": true,
"moduleNameMapper": {
"^OktaAuth(.*)$": "<rootDir>/jquery/$1"
},
"testMatch": [
"**/test/spec/*.js"
],
"testPathIgnorePatterns": [
"./test/spec/oauthUtil.js"
],
"reporters": [
"default",
"jest-junit"
]
}
4 changes: 3 additions & 1 deletion jquery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
*/

var jqueryRequest = require('./jqueryRequest');
module.exports = require('../lib/clientBuilder')(jqueryRequest);
var storageUtil = require('../lib/browser/browserStorage');

module.exports = require('../lib/browser/browser')(storageUtil, jqueryRequest);
2 changes: 1 addition & 1 deletion lib/TokenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/* eslint complexity:[0,8] max-statements:[0,21] */
var util = require('./util');
var AuthSdkError = require('./errors/AuthSdkError');
var storageUtil = require('./storageUtil');
var storageUtil = require('./browser/browserStorage');
var Q = require('q');
var Emitter = require('tiny-emitter');
var config = require('./config');
Expand Down
87 changes: 20 additions & 67 deletions lib/clientBuilder.js → lib/browser/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,34 @@
/* eslint-disable complexity */
/* eslint-disable max-statements */

require('./vendor/polyfills');

require('../vendor/polyfills');

var AuthSdkError = require('../errors/AuthSdkError');
var builderUtil = require('../builderUtil');
var config = require('../config');
var cookies = require('./browserStorage').storage;
var http = require('../http');
var oauthUtil = require('../oauthUtil');
var Q = require('q');
var oauthUtil = require('./oauthUtil');
var util = require('./util');
var tx = require('./tx');
var session = require('./session');
var cookies = require('./cookies');
var token = require('./token');
var AuthSdkError = require('./errors/AuthSdkError');
var config = require('./config');
var TokenManager = require('./TokenManager');
var http = require('./http');
var session = require('../session');
var token = require('../token');
var TokenManager = require('../TokenManager');
var tx = require('../tx');
var util = require('../util');

function OktaAuthBuilder(args) {
var sdk = this;

if (!args) {
throw new AuthSdkError('No arguments passed to constructor. ' +
'Required usage: new OktaAuth(args)');
}

var url = args.url;
if (!url) {
var isUrlRegex = new RegExp('^http?s?://.+');
if (args.issuer && isUrlRegex.test(args.issuer)) {
// Infer the URL from the issuer URL, omitting the /oauth2/{authServerId}
url = args.issuer.split('/oauth2/')[0];
} else {
throw new AuthSdkError('No url passed to constructor. ' +
'Required usage: new OktaAuth({url: "https://sample.okta.com"})');
}
}

if (url.indexOf('-admin.') !== -1) {
throw new AuthSdkError('URL passed to constructor contains "-admin" in subdomain. ' +
'Required usage: new OktaAuth({url: "https://dev-12345.okta.com})');
}

var url = builderUtil.getValidUrl(args);
this.options = {
url: util.removeTrailingSlash(url),
clientId: args.clientId,
issuer: util.removeTrailingSlash(args.issuer),
authorizeUrl: util.removeTrailingSlash(args.authorizeUrl),
userinfoUrl: util.removeTrailingSlash(args.userinfoUrl),
redirectUri: args.redirectUri,
ajaxRequest: args.ajaxRequest,
httpRequestClient: args.httpRequestClient,
storageUtil: args.storageUtil,
transformErrorXHR: args.transformErrorXHR,
headers: args.headers
};
Expand Down Expand Up @@ -97,8 +78,8 @@ function OktaAuthBuilder(args) {
};

// This is exposed so we can mock document.cookie in our tests
sdk.tx.exists._getCookie = function(name) {
return cookies.getCookie(name);
sdk.tx.exists._get = function(name) {
return cookies.get(name);
};

// This is exposed so we can mock window.location.href in our tests
Expand Down Expand Up @@ -195,20 +176,7 @@ proto.signOut = function () {
return this.session.close();
};

// { username, (relayState) }
proto.forgotPassword = function (opts) {
return tx.postToTransaction(this, '/api/v1/authn/recovery/password', opts);
};

// { username, (relayState) }
proto.unlockAccount = function (opts) {
return tx.postToTransaction(this, '/api/v1/authn/recovery/unlock', opts);
};

// { recoveryToken }
proto.verifyRecoveryToken = function (opts) {
return tx.postToTransaction(this, '/api/v1/authn/recovery/token', opts);
};
builderUtil.addSharedPrototypes(proto);

// { resource, (rel), (requestContext)}
proto.webfinger = function (opts) {
Expand Down Expand Up @@ -272,19 +240,4 @@ proto.fingerprint = function(options) {
});
};

module.exports = function(ajaxRequest) {
function OktaAuth(args) {
if (!(this instanceof OktaAuth)) {
return new OktaAuth(args);
}

if (args && !args.ajaxRequest) {
args.ajaxRequest = ajaxRequest;
}
util.bind(OktaAuthBuilder, this)(args);
}
OktaAuth.prototype = OktaAuthBuilder.prototype;
OktaAuth.prototype.constructor = OktaAuth;

return OktaAuth;
};
module.exports = builderUtil.buildOktaAuth(OktaAuthBuilder);
4 changes: 2 additions & 2 deletions lib/index.js → lib/browser/browserIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
*
*/

// This exists to use reqwest for http requests by default
module.exports = require('../reqwest');
// This exists to use reqwest for http requests by default when library used on browser
module.exports = require('../../reqwest');
38 changes: 33 additions & 5 deletions lib/storageUtil.js → lib/browser/browserStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
*
*/

var cookies = require('./cookies');
var storageBuilder = require('./storageBuilder');
var config = require('./config');
var Cookies = require('js-cookie');
var storageBuilder = require('../storageBuilder');
var config = require('../config');

// Building this as an object allows us to mock the functions in our tests
var storageUtil = {};
Expand Down Expand Up @@ -59,10 +59,10 @@ storageUtil.getSessionStorage = function() {
// Provides webStorage-like interface for cookies
storageUtil.getCookieStorage = function() {
return {
getItem: cookies.getCookie,
getItem: storageUtil.storage.get,
setItem: function(key, value) {
// Cookie shouldn't expire
cookies.setCookie(key, value, '2038-01-19T03:14:07.000Z');
storageUtil.storage.set(key, value, '2200-01-01T00:00:00.000Z');
}
};
};
Expand All @@ -78,4 +78,32 @@ storageUtil.testStorage = function(storage) {
}
};

storageUtil.storage = {
set: function(name, value, expiresAt) {
var cookieOptions = {
path: '/'
};

// eslint-disable-next-line no-extra-boolean-cast
if (!!(Date.parse(expiresAt))) {
// Expires value can be converted to a Date object.
//
// If the 'expiresAt' value is not provided, or the value cannot be
// parsed as a Date object, the cookie will set as a session cookie.
cookieOptions.expires = new Date(expiresAt);
}

Cookies.set(name, value, cookieOptions);
return storageUtil.storage.get(name);
},

get: function(name) {
return Cookies.get(name);
},

delete: function(name) {
return Cookies.remove(name, { path: '/' });
}
};

module.exports = storageUtil;
Loading

0 comments on commit c31638b

Please sign in to comment.