2.0.1 • Published 4 years ago

userin v2.0.1

Weekly downloads
2
License
BSD-3-Clause
Repository
github
Last release
4 years ago

UserIn 2.0 · License

UserIn is an NodeJS Express middleware to build Authorization Servers that support OAuth 2.0 workflows and integrate with Identity Providers (e.g., Google, Facebook, GitHub). Its openid mode exposes an API that complies to the OpenID Connect specification. With UserIn, the OAuth 2.0/OpenID Connect flows are abstracted so that developers focus only on implementing basic CRUD operations (e.g., get user by username and password, insert token's claims object) using the backend storage of their choice.

To ease testing, UserIn ships with a utility that allows to export a collection.json to Postman.

UserIn is designed to expose web APIs that support two different flow types:

  • Non-OAuth 2.0 flows. These are the ones that start with your platform's login/signup form.
  • OAuth 2.0/OpenID Connect flows. These are the ones that often(1) start with your platform's consent page so that your users can leverage some or all of your plaftorm's API via an authorized third-party.

(1) Other OAuth 2.0 authorization flows that do not require a consent page are the password, client_credentials grant type flows. Those flows are generally used for programmatic access.

Table of contents

Getting started

Creating a UserIn Authorization Server consists in creating an UserInStrategy class (which must inherit from the Strategy class) and then registering that class with the UserIn middleware. That UserInStrategy class must implement specific methods based on how many UserIn features must be supported. UserIn removes the burden of implementing OAuth 2.0 logic in those methods so developer focus only on simple CRUD implementations.

Install UserIn:

npm i userin

If you need to support authentication using Facebook, install the Facebook passport (more about other providers in the Setting up an identity provider section):

npm i passport-facebook
const express = require('express')
const app = express()
const { UserIn, Strategy, Postman } = require('userin')
const Facebook = require('passport-facebook')

class YourStrategy extends Strategy {
	constructor(config) {
		super(config)
		this.name = 'yourstrategyname',

		// loginsignup mode
		// ================
		// 		Implement those seven methods if you need to support the 'loginsignup' 
		// 		mode (i.e., allowing users to login/signup with their username and password only)
		this.create_end_user = (root, { user }, context) => { /* Implement your logic here */ }
		this.get_end_user = (root, { user }, context) => { /* Implement your logic here */ }
		this.generate_access_token = (root, { claims }, context) => { /* Implement your logic here */ }
		this.generate_refresh_token = (root, { claims }, context) => { /* Implement your logic here */ }
		this.get_refresh_token_claims = (root, { token }, context) => { /* Implement your logic here */ }
		this.get_access_token_claims = (root, { token }, context) => { /* Implement your logic here */ }
		this.delete_refresh_token = (root, { token }, context) => { /* Implement your logic here */ }

		// loginsignupfip mode
		// ===================
		// 		Add those four methods to the above five if you also need to support login and signup with Identity 
		// 		Providers such as Facebook, Google, ...
		this.create_fip_user = (root, { strategy, user }, context) => { /* Implement your logic here */ }
		this.get_fip_user = (root, { strategy, user }, context) => { /* Implement your logic here */ }
		this.generate_authorization_code = (root, { claims }, context) => { /* Implement your logic here */ }
		this.get_authorization_code_claims = (root, { token }, context) => { /* Implement your logic here */ }

		// openid mode
		// ===================
		// 		Add those thirteen methods to the following eight if you need to support all the OpenID Connect
		// 		APIs which allow third-parties to use your APIs:
		// 			1. 'generate_access_token',
		// 			2. 'generate_authorization_code',
		// 			3. 'generate_refresh_token',
		// 			4. 'get_end_user', 
		// 			5. 'get_authorization_code_claims',
		// 			6. 'get_refresh_token_claims'
		// 			7. 'get_access_token_claims'
		// 			8. 'delete_refresh_token'
		this.get_identity_claims = (root, { user_id, scopes }, context) => { /* Implement your logic here */ }
		this.get_client = (root, { client_id, client_secret }, context) => { /* Implement your logic here */ }
		this.get_id_token_claims = (root, { token }, context) => { /* Implement your logic here */ }
		this.get_auth_request_claims = (root, { token }, context) => { /* Implement your logic here */ }
		this.get_auth_consent_claims = (root, { token }, context) => { /* Implement your logic here */ }
		this.generate_id_token = (root, { claims }, context) => { /* Implement your logic here */ }
		this.generate_auth_request_code = (root, { claims }, context) => { /* Implement your logic here */ }
		this.generate_auth_consent_code = (root, { claims }, context) => { /* Implement your logic here */ }
		this.get_claims_supported = (root) => { /* Implement your logic here */ }
		this.get_scopes_supported = (root) => { /* Implement your logic here */ }
		this.link_client_to_user = (root) => { /* Implement your logic here */ }
		// Those two OpenID event handlers are optional. If they are not implemented, the UserIn middleware uses default
		// values instead:
		// 	For 'get_jwks' UserIn uses an empty array.
		// 	For 'get_grant_types_supported' UserIn uses this array: ['password', 'client_credentials', 'authorization_code', 'refresh_token']
		this.get_jwks = (root) => { /* Implement your logic here */ }
		this.get_grant_types_supported = (root) => { /* Implement your logic here */ }

		// IMPORTANT NOTE: The above event handlers support both synchronous and asynchronous implementations. Both the 
		// following are correct:
		// 		this.generate_access_token = (root, { claims }, context) => { /* Implement your logic here */ }
		// 		or 
		// 		this.generate_access_token = async (root, { claims }, context) => { /* Implement your await logic here */ }
	}
}

const userin = new UserIn({
	Strategy: YourStrategy,
	modes:['loginsignup', 'loginsignupfip', 'openid'], // You have to define at least one of those three values.
	config: {
		baseUrl: 'http://localhost:3330',
		consentPage: 'https://your-domain.com/consent-page', 	// only required when modes contains 'openid'.
		tokenExpiry: {
			access_token: 3600,
			id_token: 3600, 					// only required when modes contains 'openid'.
			code: 30 							// only required when modes contains 'loginsignupfip' or 'openid'.
		}
	}
})

// [Optional] This code requries that an app is registered with 
// Facebook first. For more details about this topic, please refer 
// to the "Setting up an identity provider" section. 
userIn.use(Facebook, {
	scopes: ['public_profile'],
	profileFields: ['id', 'displayName', 'photos', 'email', 'first_name', 'middle_name', 'last_name']
})

// [Optional] Example of how to listen to events and even modify their response. 
userIn.on('generate_access_token', (root, payload, context) => {
	console.log(`'generate_access_token' event fired. Payload:`)
	console.log(payload)
	console.log('Previous handler response:')
	console.log(root)
	console.log('Current context:')
	console.log(context)
})

// [Optional] Exposes an extra 'v1/postman/collection.json' endpoint
// More about this topic in the Integration testing section.
userIn.use(new Postman('userin-my-app'))

app.use(userIn)

app.listen(3330)

The list of exposed endpoints is detailed under the Endpoints section. That list is also discoverable via the following endpoints: All the endpoints that the UserIn middleware exposes are discoverable at the following two endpoints:

Auth modes

The idea behind those modes is to add new non-OAuth 2.0 web APIs to complement the OAuth 2.0 specification. Indeed, the challenges that OAuth 2.0 aim to fix are not related to securing your apps using your own web APIs. OAuth 2.0 is designed to let third-parties use your APIs on behalf of your users. But as a software engineer, you often need to perform both (usually starting with the first challenge to secure your APIs to power your apps). UserIn's aim is to offer an implementation strategy that is progressive. A usual progression would be: 1. Allow your users to login or create a new account using username and password. UserIn calls this the loginsignup mode. 2. Add support for login or create new account with Identity Providers (e.g., Facebook, Google). UserIn calls this the loginsignupfip mode. 3. Allow third-parties to use your API. UserIn calls this the openid mode.

Notice that until you reach the third step, you actually do not need OAuth 2.0 or OpenID.

loginsignup mode

This is the simplest group of flows to implement. It only supports login and signup with username and password. Generates short-lived access_token, and optionally long-lived refresh_token upon successfull authentication. Use it to let your users login and signup to your platform using a username and password only.

loginsignup strategy requirements

  • Constructor required fields: - baseUrl - tokenExpiry.access_token
    	> Example:
    	> ```js
    	> const strategy = new YourStrategy({ 
    	>	baseUrl: 'https://your-authorization-server-domain.com',
    	>	modes:['loginsignup'], // this is optional as the default value is ['loginsignup']
    	> 	tokenExpiry: {
    	> 		access_token: 3600
    	> 	}
    	> })
    	> ```
  • Requires five event handlers: 1. create_end_user 2. get_end_user 3. generate_access_token 4. generate_refresh_token 5. get_refresh_token_claims 6. get_access_token_claims 7. delete_refresh_token

loginsignupfip mode

Supports login and signup with username/password and Federated Identity Providers (e.g., Facebook, Google). Generates short-lived access_token, short-lived authorization code, and optionally long-lived refresh_token upon successfull authentication. This mode is a superset of the loginsignup mode. Use it to let your users login and signup to your platform using a username and password as well as one or many FIPs.

loginsignupfip strategy requirements

This mode is a superset of loginsignup.

  • Constructor required fields: - baseUrl - modes: Must contain 'loginsignupfip'. - tokenExpiry.access_token - tokenExpiry.code
    	> Example:
    	> ```js
    	> const strategy = new YourStrategy({ 
    	>	baseUrl: 'https://your-authorization-server-domain.com',
    	>	modes:['loginsignupfip'],
    	> 	tokenExpiry: {
    	> 		access_token: 3600,
    	> 		code: 30
    	> 	}
    	> })
    	> ```
  • Requires eleven event handlers: 1. create_end_user (same as loginsignup) 2. get_end_user (same as loginsignup) 3. generate_access_token (same as loginsignup) 4. generate_refresh_token (same as loginsignup) 5. get_refresh_token_claims (same as loginsignup) 6. get_access_token_claims (same as loginsignup) 7. delete_refresh_token (same as loginsignup) 8. create_fip_user 9. get_fip_user 10. generate_authorization_code 11. get_authorization_code_claims

openid mode

Supports login (no signup) using any the OpenID Connect flows (Authorization code, Implicit, Credentials and Password). Generates short-lived access_token, short-lived authorization code, short-lived id_token, and optionally long-lived refresh_token upon successfull authentication. Use it to let others systems access your platform. OpenID Connect and OAuth 2.0 powers the following use cases: - Access to your platform by a third-party directly. This flow is called the Credentials flow. - Access to your platform by a third-party on behalf of one of your user. In that case, the user has given consent to that third-party system to access some resources on your platform (this consent was given via a redirection to a consent page hosted on your platform). There are two OpenID flows that can achieve this: Authorization code flow (recommended) and the Implicit flow (deprecated). - Access to you platform by one of your user using their client_id, username and password (optionally their client_secret if your plaftorm is private). This OpenID flow is called the password flow.

openid strategy requirements

  • Constructor required fields: - baseUrl - consentPage - modes: Must contain 'openid'. - tokenExpiry.id_token - tokenExpiry.access_token - tokenExpiry.code
    	> Example:
    	> ```js
    	> const strategy = new YourStrategy({ 
    	>	baseUrl: 'https://your-authorization-server-domain.com',
    	>	consentPage: 'https://your-app-login-for-3rd-party.com',
    	>	modes:['openid'],
    	> 	tokenExpiry: {
    	> 		id_token: 3600,
    	> 		access_token: 3600,
    	> 		code: 30
    	> 	}
    	> })
    	> ```
  • Requires twenty one event handlers: 1. get_end_user (same as loginsignup and loginsignupfip) 2. generate_access_token (same as loginsignup and loginsignupfip) 3. generate_refresh_token (same as loginsignup and loginsignupfip) 4. get_refresh_token_claims (same as loginsignup and loginsignupfip) 5. get_access_token_claims (same as loginsignup and loginsignupfip) 6. delete_refresh_token (same as loginsignup and loginsignupfip) 7. generate_authorization_code (same as loginsignupfip) 8. get_authorization_code_claims (same as loginsignupfip) 9. generate_id_token 10. generate_auth_request_code 11. generate_auth_consent_code 12. get_id_token_claims 13. get_identity_claims 14. get_client 15. get_jwks 16. get_claims_supported 17. get_scopes_supported 18. get_grant_types_supported 19. get_auth_request_claims 20. get_auth_consent_claims 21. link_client_to_user

Endpoints

The number of endpoints exposed by UserIn depends on its modes. UserIn supports three modes which can be combined together: 1. loginsignup: Non-OAuth 2.0 compliant set of APIs that powers an Authorization Server that can exchange your user's username and password with an access_token, and a refresh_token. Those tokens allow your Apps to safely access your platform's API. 2. loginsignupfip: Same as the loginsignup mode with the extra ability to use an identity provider (e.g., Facebook) to access the tokens. 3. openid: OAuth 2.0 and OpenID Connect compliant set of APIs that powers an Authorization Server that support multiple flows to exchange your user's username and password with various tokens. The difference between this mode and the previous two is that your user is making that exchange request within the context of a third-party system which is uniquely identify by its client_id. That third-party system must be registered on your platform before your user can use your APIs within that context. Contrary to the first two modes, OAuth 2.0 make it possible to restrict which APIs can be used by combining the client_id with scopes. OAuth 2.0 and OpenID are not designed to support creating accounts, which explain why UserIn supports the first two modes above. The purpose of OAuth 2.0 is to let third-party systems registered on your platform with specific scopes to leverage some or all of your APIs to enhance the experience of a subset of their users that also have an account on your platform.

By default, UserIn exposes the following web APIs:

PathnameModeMethodTypeDescription
/v1/.well-known/configurationAllGETNot OAuth 2.0Discovery metadata JSON about all web API.
/v1/postman/collection.jsonAllGETNot OAuth 2.0Postman collection 2.0 definition to create a Postman client. This endpoint is not toggled by default. To toggle it, please refer to the Publishing a Postman collection as a web link section.
/v1/loginloginsignup & loginsignupfipPOSTNot OAuth 2.0Lets user log in.
/v1/signuploginsignup & loginsignupfipPOSTNot OAuth 2.0Lets user sign up.
/oauth2/v1/tokenAllPOSTOAuth 2.0Gets one or many tokens (e.g., access_token, refresh_token, id_token).
/oauth2/v1/revokeAllPOSTOAuth 2.0Revokes a refresh_token.
/oauth2/v1/.well-known/openid-configurationopenidGETOAuth 2.0Discovery metadata JSON about OpenID web API only.
/oauth2/v1/authorizeopenidGETOAuth 2.0Redirects to your platform's consent page to prompt user to authorize a third-party to access their resources.
/oauth2/v1/authorizeconsentopenidGETNon-OAuth 2.0Processes the consent page's response. Though this is technically not part of the OAuth 2.0 specification, this API is what allows UserIn to implement the full OAuth 2.0 Authorization Code flow. That's why this API is still labelled as OAuth 2.0.
/oauth2/v1/introspectopenidPOSTOAuth 2.0Introspects a token (e.g., access_token, refresh_token, id_token).
/oauth2/v1/userinfoopenidGETOAuth 2.0Returns user's profile based on the claims associated with the access_token.
/oauth2/v1/certsopenidGETOAuth 2.0Array of public JWK keys used to verify id_tokens.

Additionally, for each identity provider installed on UserIn, the following new endpoint is added (this example uses Facebook):

PathnameModeMethodTypeDescription
/v1/facebook/authorizeloginsignupfipGETNot OAuth 2.0Redirects to Facebook consent page.

To learn more about setting up identity providers, please refer to the next section.

/.well-known/configuration

  • Required modes: none. This endpoint is always available.
  • OAuth 2.0 compliant: No.
  • Description: Gets a JSON object describing where all the other endpoints are located and what type of configuration is supported.
  • HTTP method: GET
  • Parameters: none

/login

Doc under construction...

/signup

Doc under construction...

/token

  • Required modes: none. This endpoint is always available.
  • OAuth 2.0 compliant: Yes, but also support non-standard usage when the client_id is not required to support login/signup flows where a third-party is not involved.
  • Description: Exchanges credentials for tokens.
  • HTTP method: POST
  • Parameters: Depends on the grant type and the API private configuration.

refresh_token grant type

  • Supported modes: all
  • Description: With this grant type, a refresh_token is exchanged for a new access_token and potentially a new id_token if the mode is openid and is the initial scopes contained openid.
  • Body parameters: - grant_type required: refresh_token - refresh_token required: <REFRESH TOKEN VALUE> - client_id optional: Only required for OpenID clients. This means that the modes must contain openid and that the refresh_token must have been acquired via an OpenID flow (e.g., consent page). - client_secret optional: Only required when the client_id is required and that specific client is configured so that the client_secret is required.

authorization_code grant type

  • Supported modes: loginsignupfip and openid
  • Description: With this grant type, an authorization code is exchanged for an access_token and potentially: - An id_token if the mode is openid and is the initial scopes contained openid. - A refresh_token if the initial scopes contained offline_access. If the mode contains openid, then the offline_access scope must be explicitely supported by the client_id. This is configured on the get_client event handler. That authorization code is acquired via one of the following two flows: 1. The login/signup screen (loginsignupfip mode) when the user selected an identity provider (e.g., Facebook) rather than the username/password method. 2. A third-party system redirected one of your user to your platform consent page (openid mode).
  • Body parameters: - grant_type required: authorization_code - code required: <AUTHORIZATION CODE VALUE> - redirect_uri required: This is a security precaution. This redirect uri must be the same as the one that was used by the consent page to redirect to your platform to return the authorization code. - client_id optional: Only required for OpenID clients. This means that the modes must contain openid and that the authorization code must have been acquired via an OpenID flow (e.g., consent page). - client_secret optional: Only required when the client_id is required and that specific client is configured so that the client_secret is required. - code_verifier optional: This value is only required when the authorization code was acquired with a code_challenge. This security strategy is called PKCE (Proof Key for Code Exchange).

password grant type

  • Supported modes: openid
  • Description: With this grant type, a client_id, username and password are exchanged for an access_token and potentially an id_token if the scopes contain openid.
  • Body parameters: - grant_type required: password - username required: <USERNAME> - password required: <PASSWORD> - client_id required: <CLIENT_ID> - client_secret optional: Only required when the client_id is required and that specific client is configured so that the client_secret is required. - scope optional: <SPACE DELIMITED SCOPES>

client_credentials grant type

  • Supported modes: openid
  • Description: With this grant type, a client_id and a client_secret are exchanged for an access_token and potentially an id_token if the scopes contain openid.
  • Body parameters: - grant_type required: client_credentials - client_id required: <CLIENT_ID> - client_secret required: <CLIENT_SECRET> - scope optional: <SPACE DELIMITED SCOPES>

/revoke

  • Required modes: openid
  • OAuth 2.0 compliant: Yes, but also support non-standard usage when the client_id is not required to support login/signup flows where a third-party is not involved.
  • Description: Revokes a refresh_token. In theory, this method should also allow to revoke an access_token, but in practice this is not always possible. Usually, the access_token is self-signed, which means the only way to revoke it is to wait until it expires and prevent the refresh_token to be used to issue a new one, which is similar to revoke the refresh_token. This is why UserIn does not support revoking access_tokens.
  • HTTP method: POST
  • Header: - Authorization required: Must be the access_token value prefixed with the Bearer scheme (e.g., Bearer 123).
  • Body parameters: - token required: <TOKEN VALUE> - client_id optional: Only required for OpenID clients. This means that the modes must contain openid and that the refresh_token must have been acquired via an OpenID flow (e.g., consent page). - client_secret optional: Only required when the client_id is required and that specific client is configured so that the client_secret is required.

/.well-known/openid-configuration

  • Required modes: openid
  • OAuth 2.0 compliant: Yes
  • Description: Gets a JSON object describing where all the other OpenID endpoints are located and what type of OpenID configuration is supported.
  • HTTP method: GET
  • Parameters: none

/authorize

  • Required modes: openid
  • OAuth 2.0 compliant: Yes
  • Description: Redirects to your platform's consent page to prompt user to authorize a third-party to access their resources.
  • HTTP method: GET
  • Query parameters: - client_id required: <CLIENT_ID> - client_secret optional: Only required when the client identified by client_id contains one of the following values in its auth_methods property: - client_secret_basic - client_secret_post - response_type required: Valid values are: code, id_token, token, code id_token, code token, id_token token or code id_token token - redirect_uri required: - scope optional: - state optional:

/authorizeconsent

  • Required modes: openid
  • OAuth 2.0 compliant: No
  • Description: Processes the consent page's response. Though this is technically not part of the OAuth 2.0 specification, this API is what allows UserIn to implement the full OAuth 2.0 Authorization Code flow. That's why this API is still labelled as OAuth 2.0.
  • HTTP method: GET
  • Query parameters: - client_id required: <CLIENT_ID> - response_type required: Valid values are: code, id_token, token, code id_token, code token, id_token token or code id_token token - redirect_uri required: - scope optional: - state optional:

/introspect

  • Required modes: openid
  • OAuth 2.0 compliant: Yes
  • Description: Returns basic details about a token (e.g., active or not, expiry date, creation date, scopes).
  • HTTP method: POST
  • Body parameters: - token required: <TOKEN VALUE> - token_type_hint required: Valid values are: access_token, id_token and refresh_token. - client_id required: <CLIENT_ID> - client_secret optional: Only required when the client_id is required and that specific client is configured so that the client_secret is required.

/userinfo

  • Required modes: openid
  • OAuth 2.0 compliant: Yes
  • Description: Returns details about a user. The level of details depends on the scopes associated with the access_token.
  • HTTP method: GET
  • Header: - Authorization required: Must be the access_token value prefixed with the Bearer scheme (e.g., Bearer 123).

/certs

Doc under construction...

//authorize

Doc under construction...

Events and event handlers

Events overview

UserIn behaviors are managed via events and event handlers. Out-of-the-box, UserIn does not define any handlers to respond to those events. As a software engineer, this is your job to implement those event handlers in adequation with your business logic. The following list represents all the events that can be triggered during an authentication or authorization flow, but worry not, you are not forced to implement them all. You only have to implement the event handlers based on the type of authentication and authorization flow you wish to support.

  1. create_end_user
  2. create_fip_user
  3. generate_access_token
  4. generate_authorization_code
  5. generate_id_token
  6. generate_refresh_token
  7. generate_auth_request_code
  8. generate_auth_consent_code
  9. get_access_token_claims
  10. get_authorization_code_claims
  11. get_auth_request_claims
  12. get_auth_consent_claims
  13. get_client
  14. get_config
  15. get_end_user
  16. get_fip_user
  17. get_id_token_claims
  18. get_identity_claims
  19. get_refresh_token_claims
  20. get_jwks
  21. get_claims_supported
  22. get_scopes_supported
  23. get_grant_types_supported
  24. delete_refresh_token
  25. link_client_to_user
  26. get_config: Automatically implemented.

Each of those events trigger a chain of event handlers. By default, only one handler is configured in that chain (the one that you should have implemented in your UserIn Strategy). UserIn exposes an on API that allows to add more handlers for each event as shown in this example:

userIn.on('generate_access_token', (root, payload, context) => {
	console.log(`'generate_access_token' event fired. Payload:`)
	console.log(payload)
	console.log('Previous handler response:')
	console.log(root)
	console.log('Current context:')
	console.log(context)
})

root is the response returned by the previous event handler. If your handler does not return anything, root is passed to the next handler. The code above is similar to this:

userIn.on('generate_access_token', (root, payload, context) => {
	console.log(`'generate_access_token' event fired. Payload:`)
	console.log(payload)
	console.log('Previous handler response:')
	console.log(root)
	console.log('Current context:')
	console.log(context)

	return root
})

If, on the other hand, your handler returns a response, that response overrides root.

Event APIs

create_end_user

Example of that logic encapsulated in a create_end_user.js:

const { error: { wrapErrors } } = require('puffy')
const services = require('../services')

/**
 * Creates new user.
 * 
 * @param  {Object} 	root					Previous handler's response. Occurs when there are multiple handlers defined for the same event. 
 * @param  {String}		payload.user.username		
 * @param  {String}		payload.user.password		
 * @param  {String}		payload.user...			More properties
 * @param  {Object}		context					Strategy's configuration
 * 
 * @return {Object}		user					This object should always defined the following properties at a minimum.
 * @return {Object}		user.id					String ot number
 */
const handler = async (root, { user }, { repos }) => {
	// Note: The following assertions have already been checked by UserIn so this function 
	// does not need to check these again:
	// 	- 'user' is truthy. 
	// 	- 'username' is truthy
	// 	- 'password' is truthy
	// 	- 'username' does not exist already

	const errorMsg = 'Failed to create end user'

	// 1. Verify password minimal requirements
	const { valid, reason } = services.password.strongEnough(user.password)

	if (!valid)
		throw new Error(`${errorMsg}. The password is not strong enought. ${reason}`)

	const [newUserErrors, newUser] = await repos.user.insert(user)
	if (newUserErrors)
		throw wrapErrors(errorMsg, newUserErrors)

	return newUser
}

module.exports = handler

create_fip_user

Doc under construction...

generate_access_token

Example of that logic encapsulated in a generate_access_token.js:

const { error: { wrapErrors } } = require('puffy')
const tokenManager = require('../tokenManager')

/**
 * Generates a new access_token. 
 * 
 * @param  {Object} 	root				Previous handler's response. Occurs when there are multiple handlers defined for the same event. 
 * @param  {Object}		payload.claims
 * @param  {String}		payload.state		This optional value is not strictly necessary, but it could help set some context based on your own requirements.
 * @param  {Object}		context				Strategy's configuration
 * 
 * @return {String}		token
 */
const handler = async (root, { claims, state }, { repos }) => {
	// Note: The following assertions have already been checked by UserIn so this function 
	// does not need to check these again:
	// 	- 'claims' is truthy and is an object
	// 
	// This function is expected to behave following the specification described at 
	// https://github.com/nicolasdao/userin#access_token-requirements
	
	const [errors, token] = await tokenManager(repos)('access_token').create(claims)
	if (errors)
		throw wrapErrors('Failed to create access_token', errors)

	return token
}

module.exports = handler

generate_authorization_code

Doc under construction...

generate_id_token

Doc under construction...

generate_refresh_token

Example of that logic encapsulated in a generate_refresh_token.js:

const { error: { wrapErrors } } = require('puffy')
const tokenManager = require('../tokenManager')

/**
 * Generates a new refresh_token. 
 * 
 * @param  {Object} 	root				Previous handler's response. Occurs when there are multiple handlers defined for the same event. 
 * @param  {Object}		payload.claims
 * @param  {String}		payload.state		This optional value is not strictly necessary, but it could help set some context based on your own requirements.
 * @param  {Object}		context				Strategy's configuration
 * 
 * @return {String}		token
 */
const handler = async (root, { claims, state }, { repos }) => {
	// Note: The following assertions have already been checked by UserIn so this function 
	// does not need to check these again:
	// 	- 'claims' is truthy and is an object
	// 
	// This function is expected to behave following the specification described at 
	// https://github.com/nicolasdao/userin#refresh_token-requirements
	
	const [errors, token] = await tokenManager(repos)('refresh_token').create(claims)
	if (errors)
		throw wrapErrors('Failed to create refresh_token', errors)

	return token
}

module.exports = handler

generate_auth_request_code

Doc under construction...

generate_auth_consent_code

Doc under construction...

get_access_token_claims

Doc under construction...

get_authorization_code_claims

Doc under construction...

get_auth_request_claims

Doc under construction...

get_auth_consent_claims

Doc under construction...

get_client

/**
 * Gets the client's audiences, scopes and auth_methods.  
 *  
 * @param  {Object} 	root					Previous handler's response. Occurs when there are multiple handlers defined for the same event. 
 * @param  {String}		payload.client_id
 * @param  {String}		payload.client_secret	Optional. If specified, this method should validate the client_secret.
 * @param  {Object}		context					Strategy's configuration
 * 
 * @return {[String]}	output.audiences		Client's audiences.	
 * @return {[String]}	output.scopes			Client's scopes.	
 * @return {[String]}	output.auth_methods		Client's auth_methods.	
 * @return {[String]}	output.redirect_uris	Client's allowed redirect URIs
 */
const handler = (root, { client_id, client_secret }, context) => {
	const client = context.repos.client.find(x => x.client_id == client_id)
	
	if (!client)
		return null

	if (client_secret && client.client_secret != client_secret)
		throw new Error('Unauthorized access')

	return {
		audiences: client.audiences || [],
		scopes: client.scopes || [],
		auth_methods: client.auth_methods || [],
		redirect_uris: client.redirect_uris || []
	}
}

get_config

/**
 * Gets the strategy's configuration object. 
 * 
 * @param  {Object} 	root							Previous handler's response. Occurs when there 
 *                              						are multiple handlers defined for the same event. 
 * @return {String}		output.iss		
 * @return {Number}		output.expiry.id_token			
 * @return {Number}		output.expiry.access_token		
 * @return {Number}		output.expiry.refresh_token		
 * @return {Number}		output.expiry.code	
 */
const get_config = (root) => {
	console.log('get_config fired')
	console.log('Previous handler response:')
	console.log(root)
	
	return {
		iss: 'https://userin.com',
		expiry: {
			id_token: 3600,
			access_token: 3600,
			code: 30
		}
	}
}

get_end_user

Example of that logic encapsulated in a get_end_user.js:

const { error:{ InvalidCredentialsError } } = require('userin')
const { error: { wrapErrors } } = require('puffy')
const services = require('../services')

/**
 * Gets the user ID and optionnaly its associated client_ids if the 'openid' is supported.
 * If the username does not exist, a null value must be returned. However, the 'password' is optional. 
 * If the 'password' is provided, it must be verified. If the verification fails, an error of type 
 * InvalidCredentialsError must be thrown (const { error:{ InvalidCredentialsError } } = require('userin'))
 * 
 * @param  {Object} 	root					Previous handler's response. Occurs when there are multiple handlers defined for the same event. 
 * @param  {String}		payload.user.username
 * @param  {String}		payload.user.password
 * @param  {String}		payload.user...			More properties
 * @param  {String}		payload.client_id		Optional. Might be useful for logging or other custom business logic.
 * @param  {String}		payload.state			Optional. Might be useful for logging or other custom business logic.
 * @param  {Object}		context					Strategy's configuration
 * 
 * @return {Object}		user					This object should always defined the following properties at a minimum.
 * @return {Object}		user.id					String ot number
 * @return {[Object]}	user.client_ids		
 */
const handler = async (root, { user, client_id, state }, { repos }) => {
	// Note: The following assertions have already been checked by UserIn so this function 
	// does not need to check these again:
	// 	- 'user' is truthy. 
	// 	- 'user.username' is truthy
	// 
	// This function is expected to behave as follow:
	// 	- If the 'username' does not exist, a null value must be returned. 
	// 	- The 'password' is optional. 
	// 	- If the 'password' is provided, it must be verified. If the verification fails, an error of type 
	// 	InvalidCredentialsError must be thrown (const { error:{ InvalidCredentialsError } } = require('userin'))

	const errorMsg = 'Failed to get end user'

	const [confirmedUserErrors, confirmedUser] = await repos.user.find({ where:{ email:user.username } })
	if (confirmedUserErrors)
		throw wrapErrors(errorMsg, confirmedUserErrors)

	if (!confirmedUser)
		return null

	if (user.password) {
		const eMsg = `${errorMsg}. Invalid username or password.`
		const saltedPassword = confirmedUser.password
		if (!saltedPassword || !confirmedUser.salt)
			throw new InvalidCredentialsError(eMsg)

		const valid = services.password.verify({ 
			password:user.password, 
			salt:confirmedUser.salt, 
			hashedSaltedPassword:confirmedUser.password 
		})

		if (!valid)
			throw new InvalidCredentialsError(eMsg) 
	}

	return {
		id: confirmedUser.id,
		client_ids:[]
	}
}

module.exports = handler

get_fip_user

Doc under construction...

get_id_token_claims

Doc under construction...

get_identity_claims

Doc under construction...

get_refresh_token_claims

Example of that logic encapsulated in a get_refresh_token_claims.js:

const { error: { wrapErrors } } = require('puffy')
const tokenManager = require('../tokenManager')

/**
 * Gets the refresh_token's claims
 * 
 * @param  {Object} 	root				Previous handler's response. Occurs when there are multiple handlers defined for the same event. 
 * @param  {Object}		payload.token
 * @param  {Object}		context				Strategy's configuration
 * 
 * @return {Object}		claims				This object should always defined the following properties at a minimum.
 * @return {String}		claims.iss			
 * @return {Object}		claims.sub			String or number
 * @return {String}		claims.aud
 * @return {Number}		claims.exp
 * @return {Number}		claims.iat
 * @return {Object}		claims.client_id	String or number
 * @return {String}		claims.scope
 */
const handler = async (root, { token }, { repos }) => {
	// Note: The following assertions have already been checked by UserIn so this function 
	// does not need to check these again:
	// 	- 'token' is truthy and is a string
	// 
	// This function is expected to behave following the specification described at 
	// https://github.com/nicolasdao/userin#refresh_token-requirements
	
	const [errors, refresh_token] = await tokenManager(repos)('refresh_token').getClaims(token)
	if (errors)
		throw wrapErrors('Failed to create refresh_token', errors)

	return refresh_token
}

module.exports = handler

get_jwks

Doc under construction...

get_claims_supported

Doc under construction...

get_scopes_supported

Doc under construction...

get_grant_types_supported

Doc under construction...

delete_refresh_token

Doc under construction...

link_client_to_user

Doc under construction...

OpenID Connect tokens & authorization code requirements

If you're implementing a UserIn strategy that supports the openid mode, then you must generate your tokens and authorization code following strict requirements.

id_token requirements

Doc under construction...

access_token requirements

Doc under construction...

refresh_token requirements

Doc under construction...

Authorization code requirements

Doc under construction...

Setting up an identity provider

UserIn supports both Passport strategies and native OpenID providers via their .well-known/openid-configuration discovery endpoint (e.g., https://accounts.google.com/.well-known/openid-configuration). In both cases, an app must be registered with each identity provider. The annex of this document details the steps to set this up for some of the most popular provider in the Registering an application with an Identity Provider section.

Using Passport

Example of npm Passport packages:

  • Facebook: npm i passport-facebook
  • Google: npm i passport-google-oauth20
  • GitHub: npm i passport-github
  • LinkedIn: npm i passport-linkedin-oauth2

The next example uses Facebook:

const { UserIn } = require('userin')
const Facebook = require('passport-facebook')
const YourStrategy = require('./src/YourStrategy.js')

const userin = new UserIn({
	Strategy: YourStrategy,
	modes:['loginsignupfip', 'openid'], // You have to define at least one of those three values.
	config: {
		baseUrl: 'http://localhost:3330',
		consentPage: 'https://your-domain.com/consent-page', 	// only required when modes contains 'openid'.
		tokenExpiry: {
			access_token: 3600,
			id_token: 3600, 					// only required when modes contains 'openid'.
			code: 30 							// only required when modes contains 'loginsignupfip' or 'openid'.
		}
	}
})

userIn.use(Facebook, {
	clientID: '12234',
	clientSecret: '54332432',
	scopes: ['public_profile'],
	profileFields: ['id', 'displayName', 'photos', 'email', 'first_name', 'middle_name', 'last_name']
})

NOTES: - Both the clientID and clientSecret could have been omitted when the following two environment variables are set: - FACEBOOK_CLIENT_ID - FACEBOOK_CLIENT_SECRET The convention to set up environment variables is to prefix _CLIENT_ID and _CLIENT_SECRET with the Passport's name in uppercase. - The rest of the configuration is the same as what is described on the Passport package documentation.

Using an OpenID discovery endpoint

To this day (Oct. 2020), Google is the only major player to have adopted OpenID. The others have implemented specialized version of OAuth 2.0 (Facebook has rolled out their own implementation of OpenID Connect called Facebook Connect).

const { UserIn } = require('userin')
const YourStrategy = require('./src/YourStrategy.js')

const userin = new UserIn({
	Strategy: YourStrategy,
	modes:['loginsignupfip', 'openid'], // You have to define at least one of those three values.
	config: {
		baseUrl: 'http://localhost:3330',
		consentPage: 'https://your-domain.com/consent-page', 	// only required when modes contains 'openid'.
		tokenExpiry: {
			access_token: 3600,
			id_token: 3600, 					// only required when modes contains 'openid'.
			code: 30 							// only required when modes contains 'loginsignupfip' or 'openid'.
		}
	}
})

userIn.use({
	name:'google',
	client_id: '12234',
	client_secret: '54332432',
	discovery: 'https://accounts.google.com/.well-known/openid-configuration',
	scopes:['profile', 'email']
})

NOTES: - Both the client_id and client_secret could have been omitted when the following two environment variables are set: - GOOGLE_CLIENT_ID - GOOGLE_CLIENT_SECRET The convention to set up environment variables is to prefix _CLIENT_ID and _CLIENT_SECRET with the name value.

Implementation guidelines

Because OAuth 2.0 flows are not stateless we recommend to implement your UserIn strategy using dependency injection. This will greatly help with unit testing.

Creating a UserIn Strategy class

Doc under construction...

Unit testing

Testing a UserIn Strategy class

UserIn ships with a suite of Mocha unit tests. To test your own strategy:

  1. Install mocha and chai:
npm i -D mocha chai
  1. Create a new test folder in your project root directory.
  2. Under that test folder, create a new strategy.js (or whatever name you see fit), and paste code similar to the following:
const { testSuite } = require('userin')
const { YourStrategyClass } = require('../src/yourStrategy.js')

const options = { skip:'' } // Does not skip any test.

// To test a stragegy in 'loginsignup' mode, the following minimum config is required.
const config = {
	tokenExpiry: {
		access_token: 3600
	}
}

// The required stub's properties are (change the values to your own stub):
const stub = {
	user: {
		username: 'valid@example.com', // Valid username in your own stub data.
		password: '123456' // Valid password in your own stub data.
	},
	newUserPassword: 'd32def32feq' // Add the password that will be used to test new users
}

testSuite.testLoginSignup(YourStrategyClass, config, stub, options)
  1. Add a new test script in your package.json:
	"scripts": {
		"test": "mocha --exit"
	}
  1. Run the test:
npm test

testSuite API

The testSuite API exposes four different test suite, one for each mode + one that combines all the modes. Each test suite uses the same signature: 1. testLoginSignup function 2. testLoginSignupFIP function 3. testOpenId function 4. testAll function

The signature is (YourStrategyClass: UserInStrategy, config: Object, stub: Object[, options: Object]) where:

  • YourStrategyClass is a custom UserIn Strategy class (warning: do not use an instance, use the class).
  • config is the required argument that you would pass to the YourStrategyClass constructor.
  • stub is the required fake data used to unit test the YourStrategyClass flows.
  • options is the optional object that help skip some tests or show more test results: - options.skip: [String]: Array of test to skip. To skip all test, use skip: ['all']. - options.only: [String]: Array of test to run. - options.showResults: [String]: Array of test assertions. When this array is specified, more details about the assertion outcome are displayed. Example: showResults:['login.handler.09,10', 'signup.handler.01']

testLoginSignup function

Runs the following tests:

  • strategy
  • login
  • signup
const { testSuite } = require('userin')
const { YourStrategyClass } = require('../src/yourStrategy.js')

// Use the 'option' value to control which test is run. By default, all tests are run.
// Valid test names are: 'all', 'strategy', 'login', 'signup'
// 
// const options = { skip:'all' } // Skips all tests in this suite.
// const options = { skip:'login' } // Skips the 'login' test in this suite.
// const options = { skip:['login', 'signup'] } // Skips the 'login' and 'signup' tests in this suite.
// const options = { only:'login' } // Only run the 'login' test in this suite.
// const options = { only:['login', 'signup'] } // Only run the 'login' and 'signup' tests in this suite.
const options = { skip:'', showResults:['login.handler.09,10'] } 	// Does not skip any test and show the results of:
																	// - Test 'login.handler.09'
																	// - Test 'login.handler.10'

// To test a stragegy in 'loginsignup' mode, the following minimum config is required.
const config = {
	tokenExpiry: {
		access_token: 3600
	}
}

// The required stub's properties are (change the values to your own stub):
const stub = {
	user: {
		id: 1,
		username: 'valid@example.com', // Valid username in your own stub data.
		password: '123456' // Valid password in your own stub data.
	},
	newUserPassword: 'd32def32feq' // Add the password that will be used to test new users
}

testSuite.testLoginSignup(YourStrategyClass, config, stub, options)

testLoginSignupFIP function

Runs the following tests:

  • strategy
  • login
  • signup
  • fiploginsignup
const { testSuite } = require('userin')
const { YourStrategyClass } = require('../src/yourStrategy.js')

// Use the 'option' value to control which test is run. By default, all tests are run. 
// Valid test names are: 'all', 'strategy', 'login', 'signup', 'fiploginsignup'
// 
// const options = { skip:'all' } // Skips all tests in this suite.
// const options = { skip:'login' } // Skips the 'login' test in this suite.
// const options = { skip:['login', 'signup'] } // Skips the 'login' and 'signup' tests in this suite.
// const options = { only:'login' } // Only run the 'login' test in this suite.
// const options = { only:['login', 'signup'] } // Only run the 'login' and 'signup' tests in this suite.
const options = { skip:'' } // Does not skip any test.

// To test a stragegy in 'loginsignupfip' mode, the following minimum config is required.
const config = {
	tokenExpiry: {
		access_token: 3600,
		code: 30
	}
}

// The required stub's properties are (change the values to your own stub):
const stub = {
	user: {
		id: 1,
		username: 'valid@example.com', // Valid username in your own stub data.
		password: '123456' // Valid password in your own stub data.
	},
	newUserPassword: 'd32def32feq', // Add the password that will be used to test new users
	fipUser: { // this user should be different from the one above.
		id: '1N7fr2yt', // ID of the user in the identity provider plaftform
		fipName: 'facebook', // Identity provider's name
		userId: 2 // ID of the user on your platform
	}
}

testSuite.testLoginSignupFIP(YourStrategyClass, config, stub, options)

testOpenId function

Runs the following tests:

  • strategy
  • introspect
  • token
  • userinfo
  • revoke
  • discovery
  • authorize
const { testSuite } = require('userin')
const { YourStrategyClass } = require('../src/yourStrategy.js')

// Use the 'option' value to control which test is run. By default, all tests are run. 
// Valid test names are: 'all', 'strategy', 'introspect', 'token', 'userinfo'
// 
// const options = { skip:'all' } // Skips all tests in this suite.
// const options = { skip:'introspect' } // Skips the 'introspect' test in this suite.
// const options = { skip:['introspect', 'token'] } // Skips the 'introspect' and 'token' tests in this suite.
// const options = { only:'introspect' } // Only run the 'introspect' test in this suite.
// const options = { only:['introspect', 'token'] } // Only run the 'introspect' and 'token' tests in this suite.
const options = { skip:'' } // Does not skip any test.

// To test a stragegy in 'openid' mode, the following minimum config is required.
const config = {
	openid: {
		iss: 'https://www.userin.com',
		tokenExpiry: {
			id_token: 3600,
			access_token: 3600,
			code: 30
		}
	}
}

// The required stub's properties are (change the values to your own stub):
const stub = {
	client: { 
		id: 'client_with_at_least_one_user_and_no_auth_methods',
		secret: '98765', 
		aud: 'https://private-api@mycompany.com',
		user: { 
			id: 1,
			username: 'valid@example.com', // Valid username in your own stub data.
			password: '123456' // Valid password in your own stub data.
			claimStubs: [{ // Define the identity claims you want to support here and fill the value for the 'valid@example.com' user.
				scope:'profile',
				claims: {
					given_name: 'Nic',
					family_name: 'Dao',
					zoneinfo: 'Australia/Sydney'
				}
			}, {
				scope:'email',
				claims: {
					email: 'nic@cloudlessconsulting.com',
					email_verified: true
				}
			}, {
				scope:'phone',
				claims: {
					phone: '+61432567890',
					phone_number_verified: false
				}
			}, {
				scope:'address',
				claims: {
					address: 'Castle in the shed'
				}
			}]
		}
	},
	altClient: { 
		id: 'another_client_with_no_auth_methods', 
		secret: '3751245'
	},
	privateClient: { 
		// this client must have its 'auth_methods' set to ['client_secret_basic'], ['client_secret_post'] or 
		// ['client_secret_basic', 'client_secret_post']
		id: 'yet_another_client_with_auth_methods', 
		secret: '3751245'
	}
}

testSuite.testOpenId(YourStrategyClass, config, stub, options)

testAll function

This test function tests all the previous three tests at once. Use it if you have created a UserIn Strategy class that imlements all the event handlers. The signature is the same as for the other tests. Merge all the stubs from the previous tests into a single stub object.

Dependency injection

The test suite supports inversion of control via dependency injection. All the event handlers supports the same signature:

(root: Object, payload: Object, context: Object).

For example:

YourStrategyClass.prototype.get_end_user = (root, { user }, context) => {
	const existingUser = USER_STORE.find(x => x.email == user.username)
	if (!existingUser)
		return null
	if (user.password && existingUser.password != user.password)
		throw new Error('Incorrect username or password')

	const client_ids = USER_TO_CLIENT_STORE.filter(x => x.user_id == existingUser.id).map(x => x.client_id)

	return {
		id: existingUser.id,
		client_ids
	}
}

This example shows that get_end_user depends on the USER_STORE and USER_TO_CLIENT_STORE to function. Those would typically be connectors that can perform IO queries to your backend storage. This code is not properly designed to support unit testing, especially if you are tryng to test inserts. To solve this problem, the best practice is to inject those dependencies from the outside.

This is one the purpose of the context object. The context object is the config object passed to the YourStrategyClass instance:

const { testSuite } = require('userin')
const { YourStrategyClass } = require('../src/yourStrategy.js')

// To test a stragegy in 'loginsignup' mode, the following minimum config is required.
const config = {
	tokenExpiry: {
		access_token: 3600
	},
	repos: {
		user: {
			find: (userId)
		}
	}
}

// The required stub's properties are (change the values to your own stub):
const stub = {
	user: {
		username: 'valid@example.com', // Valid username in your own stub data.
		password: '123456' // Valid password in your own stub data.
	},
}

testSuite.testLoginSignup(YourStrategyClass, config, stub, options)

In this example, let's modified the config as follow:

const config = {
	tokenExpiry: {
		access_token: 3600
	},
	repos: {
		user: USER_STORE,
		userToClient: USER_TO_CLIENT_STORE
	}
}

With this change, the get_end_user can be rewritten as follow:

YourStrategyClass.prototype.get_end_user = (root, { user }, context) => {
	const existingUser = context.repos.user.find(x => x.email == user.username)
	if (!existingUser)
		return null
	if (user.password && existingUser.password != user.password)
		throw new Error('Incorrect username or password')

	const client_ids = context.repos.userToClient.filter(x => x.user_id == existingUser.id).map(x => x.client_id)

	return {
		id: existingUser.id,
		client_ids
	}
}

This design pattern is called dependency injection. It allows to replace the behaviors from the outside. The following snippet shows how to inject dependencies in the UserIn middleware rather than on the Strategy:

const { someDependency } = require('../src/dependencies')
const userIn = new UserIn({
	Strategy: MockStrategy,
	modes:['loginsignup', 'loginsignupfip', 'openid'], // You have to define at least one of those three values.
	config: {
		baseUrl: 'http://localhost:3330',
		consentPage: 'https://your-domain.com/consent-page',
		tokenExpiry: {
			access_token: 3600,
			id_token: 3600,
			code: 30
		}
		someDependency
	}
})

Integration testing

Exporting the API to Postman

UserIn can publish its API documentation using Postman Collection v2.1. There are two ways to export a Postman collection: 1. Publish a new web endpoint at {{YOUR_DOMAIN}}/v1/postman/collection.json and use that link in Postman to import that collection. 2. Export the collection in a local file and then import that file in Postman.

Publishing a Postman collection as a web link

Use this API:

userIn.use(new Postman('your-collection-name'))

The full example looks like this:

const express = require('express')
const app = express()
const Facebook = require('passport-facebook')
const { UserIn, Postman } = require('userin')
const YourStrategy = require('./src/YourStrategy')

const userIn = new UserIn({
	Strategy: YourStrategy,
	modes:['loginsignupfip', 'openid'], // You have to define at least one of those three values.
	config: {
		baseUrl: 'http://localhost:3330',
		consentPage: 'https://your-domain.com/consent-page', 	// only required when modes contains 'openid'.
		tokenExpiry: {
			access_token: 3600,
			id_token: 3600, 					// only required when modes contains 'openid'.
			code: 30 							// only required when modes contains 'loginsignupfip' or 'openid'.
		}
	}
})

userIn.use(Facebook, {
	scopes: ['public_profile'],
	profileFields: ['id', 'displayName', 'photos', 'email', 'first_name', 'middle_name', 'last_name']
})

userIn.use({
	name:'google',
	discovery: 'https://accounts.google.com/.well-known/openid-configuration',
	scopes:['profile', 'email']
})

userIn.use(new Postman('userin-my-app'))

app.use(userIn)
app.listen(3330, () => console.log('UserIn listening on https://localhost:3330'))

Export a Postman collection in a local file

Once the UserIn instance has been created and configured, use the Postman utility as follow:

Postman.export({
	userIn,
	name: 'userin-my-app',
	path: './postman-collection.json'
})

The full example looks like this:

const express = require('express')
const app = express()
const Facebook = require('passport-facebook')
const { UserIn, Postman } = require('userin')
const YourStrategy = require('./src/YourStrategy')

const userIn = new UserIn({
	Strategy: YourStrategy,
	modes:['loginsignupfip', 'openid'], // You have to define at least one of those three values.
	config: {
		baseUrl: 'http://localhost:3330',
		consentPage: 'https://your-domain.com/consent-page', 	// only required when modes contains 'openid'.
		tokenExpiry: {
			access_token: 3600,
			id_token: 3600, 					// only required when modes contains 'openid'.
			code: 30 							// only required when modes contains 'loginsignupfip' or 'openid'.
		}
	}
})

userIn.use(Facebook, {
	scopes: ['public_profile'],
	profileFields: ['id', 'displayName', 'photos', 'email', 'first_name', 'middle_name', 'last_name']
})

userIn.use({
	name:'google',
	discovery: 'https://accounts.google.com/.well-known/openid-configuration',
	scopes:['profile', 'email']
})

Postman.export({
	userIn,
	name: 'userin-my-app',
	path: './postman-collection.json'
})

app.use(userIn)
app.listen(3330, () => console.log('UserIn listening on https://localhost:3330'))

When this code is executed, a postman-collection.json file is autogenerated. Use Postman to import the collection using this file.

Authorization code flow implementation

  1. User-agent browses to the /oauth2/v1/authorize URL passing in the query parameters the following properties:
2.0.1

4 years ago

2.0.0

4 years ago

1.16.0

4 years ago

1.15.0

4 years ago

1.14.0

4 years ago

1.13.1

4 years ago

1.12.4

4 years ago

1.9.0

4 years ago

1.8.0

4 years ago

1.7.0

4 years ago

1.6.0

4 years ago

1.5.3

4 years ago

1.5.2

4 years ago

1.5.1

4 years ago

1.5.0

4 years ago

1.4.0

4 years ago

1.3.1

4 years ago

1.3.0

4 years ago

1.2.0

4 years ago

1.1.0

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago