npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@veracity/node-auth

v3.0.11

Published

A library for authenticating with Veracity and retrieving one or more access tokens for communicating with APIs.

Downloads

104

Readme

Veracity Authentication library for NodeJS

npm version package dependencies Known Vulnerabilities Issues Downloads

This library provides utilities that help with authentication against the Veracity Identity Provider. It's based on the robust libraries passport and the strategy passport-azure-ad.

Features

  • PassportJS authentication strategy for web applications using the Veracity Identity Provider.
  • Helper for setting up authentication with Veracity as well as logout.
  • Middleware for refreshing tokens.
  • Support for encrypting session data at-rest.

Table of contents

Installation

Run npm i @veracity/node-auth or yarn add @veracity/node-auth to install. TypeScript types are included in the package.

Examples

See one of the examples to get started.

Usage

The helper setupWebAppAuth simplifies setting up authentication towards Veracity. If you're looking for an alternative, you can use passport in combination with passport-azure-ad.

const express = require("express")
const https = require("https")
const app = express()
const { setupWebAppAuth, generateCertificate } = require("@veracity/node-auth")
const { MemoryStore } = require("express-session")

const { refreshTokenMiddleware } = setupWebAppAuth({
	app,
	session: {
		secret: "your-long-super-secret-secret-here",
		store: new MemoryStore() // Notice! Only use MemoryStore for development
	},
	strategy: { // Fill these in with values from your Application Credential
		clientId: "<your-client-id>",
		clientSecret: "<your-client-secret>",
		replyUrl: "https://localhost:3000/auth/oidc/loginreturn" // make sure to update with your own replyUrl
	}
})

// This endpoint will return our user data so we can inspect it.
app.get("/user", (req, res) => {
	if (req.isAuthenticated()) {
		res.send(req.user)
		return
	}
	res.status(401).send("Unauthorized")
})

// Create an endpoint where we can refresh the services token.
app.get("/refresh", refreshTokenMiddleware(), (req, res) => {
	res.send("Refreshed token!")
})

// Set up the HTTPS development server
const server = https.createServer({
	...generateCertificate() // Generate self-signed certificates for development
}, app)

server.on("error", (error) => { // If an error occurs halt the application
	console.error(error)
	process.exit(1)
})

server.listen(3000, () => { // Begin listening for connections
	console.log("Listening for connections on port 3000")
})

setupWebAppAuth register the following default routes:

  • /login: Path used to initialize the login process
  • /logout: Path used to log out the user and delete session data
  • /error: Where the user is redirected if there are errors in the login process

Passing state

Sometimes it is useful to be able to pass data from before the login begins all the way through the authentication process until control is returned back to your code. This library supports this in two ways for web and native applications (the bearer token validation strategy does not support this):

  1. Any query parameters sent to the login handler are mirrored onto the request object when the login completes. This means you can inspect req.query in the POST handler (or onLoginComplete) when the authentication completes and see the same ones that were sent to the login request. Returning query parameters from Veracity IDP will take presedence.
  2. You can modify the request object in a handler before beginning the authentication process by adding data to the veracityAuthState property. Any data found here will be mirrored onto the final request object once the login completes. req.veracityAuthState before login will equal req.veracityAuthState after login.

To pass state using the helper functions for web and native applications you simply provide an onBeforeLogin and an onLoginComplete function to set the state and read the state respectively. Since query parameters are always mirrored if you only want to use those you do not need to provide an onBeforeLogin function at all.

// Setup is identical for setupNativeAppAuth
setupWebAppAuth({
	// ... other settings are omitted for brevity
	onBeforeLogin: (req, res, next) => {
		req.veracityAuthState = JSON.stringify({redirect: "/here"})
		next()
	},
	onLoginComplete: (req, res, next) => {		
		const state = JSON.parse(req.veracityAuthState)
		res.redirect(state.redirect || "/")
	}
})

Check if the user is logged in

Call the method req.isAuthenticated() to see if the user is logged in. Returns a boolean.

Authentication process

The authentication process used by Veracity is called Open ID Connect with token negotiation using Authorization Code Flow. Behind the scenes, Veracity relies on Microsoft Azure B2C to perform the actual login. You can read more about the protocol on Microsoft's website.

This library provides you with a strategy that you can use to perform authentication. The strategy is compatible with PassportJS and allows any Connect-compatible library to authenticate with Veracity. The technicalities of the protocol are then handled by the library and you can focus on utilizing the resulting tokens to call APIs and build your application.

Refresh token

The library also gives you a way to refresh the token. The method is returned to you when calling the setupWebAppAuth method:

const { refreshTokenMiddleware } = setupWebAppAuth(/* ... config ... */)

Later, use the refreshTokenMiddleware method like this:

app.get("/refresh", refreshTokenMiddleware(), (req, res, next) => {
	res.send("OK, token refreshed")
})

If you provide your own onVerify function to setupWebAppAuth and want to store the tokens in some specific location, you can pass in the optional arguments for resolving refresh token and storing the access token like so:

const resolveRefreshToken = (req) => req.user.customTokenPlacement.refreshToken
const storeRefreshedTokens = (refreshResponse, req) => {
	req.user.customTokenPlacement = {
		accessToken: refreshResponse.access_token,
		refreshToken: refreshResponse.refresh_token
	}
}
app.get("/refresh", refreshTokenMiddleware(resolveRefreshToken, storeRefreshedTokens), (req, res, next) => {
	res.send("OK, token refreshed")
})

Logging out

Logging out users is a relatively simple process. Your application is storing session information (user data including access tokens) within some kind of session storage. This must be removed. Then you need to redirect users to the sign out page on Veracity to centrally log them out. This last step is required by Veracity to adhere to security best-practices. The logout url is set up bt default to "/logout", but you can change it by passing logoutPath in the configuration object of setupWebAppAuth.

The URL on Veracity where you should direct users logging out is stored as a constant in this library:

const { VERACITY_LOGOUT_URL } = require("@veracity/node-auth")

app.get("/logout", (req, res) => {
	req.logout()
	res.redirect(VERACITY_LOGOUT_URL)
})

Error handling

Any error that occurs within a strategy provided by this library (or by extension a helper function) will be an instance of a VIPDError. VIDPError objects are extensions of regular Error objects that contain additional information about what type of error occured. Using this information you can decide how to proceed.

The properties of a VIDPError object are: Property|Type|Description -|-|- code|string|A unique code for this error corresponding to an error code from any of the *ErrorCodes interfaces (see below). description|string|A more detailed description of the error useful for debugging. source|string|A source for where the error occured within the library. details?|any|An object defining more details about the error in a machine readable format. innerError?|Error|If this error instance was created based on another error this property will contain that specific error instance.

Should an error occur during any part of the authentication process it will be passed to next() just like other errors in Connect-compatible applications like ExpressJS. You should handle these errors according to the documentation from your library of choice. You can find more information on error handling in Connect here and ExpressJS here.

const { VIDPError } = require("@veracity/node-auth")

// Register an error handler in your application
app.use((err, req, res, next) => {
	if (err instanceof VIDPError) { // This is an error that occured with the Veracity Authentication strategy
		// Check err.details.error for the type and act accordingly
	}
})

Logging

You can pass in a custom logger when using setupWebAppAuth. Example:

const express = require("express")
const { MemoryStore } = require("express-session")
const { setupWebAppAuth } = require("../../dist")
const winston = require("winston")

const app = express()

setupWebAppAuth({
	app,
	strategy: {
		clientId: "...",
		clientSecret: "...",
		replyUrl: "https://localhost:3000/auth/oidc/loginreturn"
	},
	session: {
		secret: "...",
		store: new MemoryStore()
	},
	logger: winston.createLogger({
		transports: [
			new winston.transports.Console(),
		]
	  })
})

Data structures

⭐️

The library makes use of several data structures. They are all defined as TypeScript interfaces that will be visible in any TypeScript aware editor. Below is an export of all public types.

IDefaultAuthConfig

Property|Type|Description -|-|- loginPath|string| logoutPath|string| errorPath|string| logLevel|LogLevel| name|string| oidcConfig|Omit<IOIDCStrategyOption, "clientID" | "redirectUrl">| policyName|string| tenantID|string| onLogout|(req: Request, res: Response, next: NextFunction) => void| onBeforeLogin|(req: Request, res: Response, next: NextFunction) => void| onVerify|VerifyOIDCFunctionWithReq| onLoginComplete|(req: Request, res: Response, next: NextFunction) => void|

IFullAuthConfig

extends Omit<IDefaultAuthConfig, "oidcConfig">

Property|Type|Description -|-|- oidcConfig|IOIDCStrategyOption| session|IMakeSessionConfigObjectOptions| additionalAuthenticateOptions?|IExtraAuthenticateOptions|Additional options passed to passport.authenticate

IExtraAuthenticateOptions

Property|Type|Description -|-|- extraAuthReqQueryParams?|{[key: string]: string | number | boolean}|

ILoggerLike

Property|Type|Description -|-|- info|(str: any) => void| warn|(str: any) => void| error|(str: any) => void| levels?|(str: any) => void|

IRouterLike

extends Pick<Router, "use" | "get" | "post">

Property|Type|Description -|-|-

ISetupWebAppAuthSettings

Property|Type|Description -|-|- app|IRouterLike|The express application to configure or the router instance. errorPath?|string|Where to redirect user on error loginPath?|string|The path where login will be configured logoutPath?|string|The path where logout will be configured logLevel?="error"|LogLevel|Logging level session|IMakeSessionConfigObjectOptions|Session configuration for express-session strategy|IVIDPWebAppStrategySettings|Configuration for the strategy you want to use. name?="veracity-oidc"|string|Name of the passport strategy policyName?="B2C_1A_SignInWithADFSIdp"|string|Policy to use when logging in. onBeforeLogin?|(req: Request & {veracityAuthState?: any}, res: Response, next: NextFunction) => void|Provide a function that executes before the login process starts.It executes as a middleware so remember to call next() when you are done. onVerify?|VerifyOIDCFunctionWithReq|The verifier function passed to the strategy.If not defined will be a passthrough verifier that stores everything from the strategy on req.user. onLoginComplete?|(req: Request, res: Response, next: NextFunction) => void,|A route handler to execute once the login is completed.The default will route the user to the returnTo query parameter path or to the root path. onLogout?|(req: Request & {veracityAuthState?: any}, res: Response, next: NextFunction) => void,|A route handler to execute once the user tries to log out.The default handler will call req.logout() and redirect to the default Veracity central logout endpoint. logger?|ILoggerLike|Optional provide your own logger

IVIDPAccessTokenPayload

extends IVIDPJWTTokenPayloadCommonClaims

Property|Type|Description -|-|- azp|string| userId|string|The users unique ID within Veracity. dnvglAccountName|string|The account name for the user. myDnvglGuid⬇|string|Deprecated: - The old id for the user. oid|string|An object id within the Veracity IDP. Do not use this for user identification@see userId upn|string| scp|string|

IVIDPAccessTokenData

extends IVIDPJWTTokenData

Property|Type|Description -|-|- scope|string|The scope this token is valid for. refreshToken?|string|If a refresh token was negotiated it will be contained here.

IVIDPAccessToken

extends IVIDPJWTToken

Property|Type|Description -|-|-

IVIDPWebAppStrategySettings

Property|Type|Description -|-|- clientId|string|The client id from the Application Credentials you created in the Veracity for Developers Provider Hub. clientSecret?|string|The client secret from the Application Credentials you created in the Veracity for Developers Provider Hub.Required for web applications, but not for native applications. replyUrl|string|The reply url from the Application Credentials you created in the Veracity for Developers Provider Hub. apiScopes?=["https://dnvglb2cprod.onmicrosoft.com/83054ebf-1d7b-43f5-82ad-b2bde84d7b75/user_impersonation"]|string[]|The scopes you wish to authenticate with. An access token will be retrieved for each api scope.If you only wish to authenticate with Veracity you can ignore this or set it to an empty array to slightly improve performance. metadataURL?=VERACITY_METADATA_ENDPOINT|string|The url where metadata about the IDP can be found.Defaults to the constant VERACITY_METADATA_ENDPOINT. additionalAuthenticateOptions?|IExtraAuthenticateOptions|Additional options passed to passport.authenticate

IVIDPJWTTokenHeader

Property|Type|Description -|-|- typ|string|The type of token this is. alg|string|The message authentication code algorithm. kid|string|The id of the key used to sign this token.

IVIDPJWTTokenData

Property|Type|Description -|-|- token|string|The full token string header|IVIDPJWTTokenHeader|Header information from the token payload|TPayload|The token payload issued|number|Unix timestamp for when the token was issued. lifetime|number|The number of seconds this token is valid for. expires|number|Unix timestamp for when the token expires.

IVIDPJWTTokenPayloadCommonClaims

Property|Type|Description -|-|- iss|string|Issuer sub|"Not supported currently. Use oid claim."|Subject aud|string|Audience exp|number|Expiration time. nbf|number|Not valid before time. iat|number|Issued at time. email|string[]| nonce|string| given_name|string| family_name|string| name|string| ver|"1.0"|

IVIDPJWTToken

Property|Type|Description -|-|- header|IVIDPJWTTokenHeader| payload|TPayload| signature|string|

VIDPRequestErrorCodes

Property|Type|Description -|-|- "read_timeout"|"read_timeout"|A timeout occured when waiting to read data from the server. "connect_timeout"|"connect_timeout"|A timeout occurred when waiting to establish a connection to the server. "status_code_error"|"status_code_error"|The request returned a non 200 status code.

VIDPAccessTokenErrorCodes

Property|Type|Description -|-|- "invalid_request"|"invalid_request"|Protocol error, such as a missing required parameter. "invalid_grant"|"invalid_grant"|The authorization code or PKCE code verifier is invalid or has expired. "unauthorized_client"|"unauthorized_client"|The authenticated client isn't authorized to use this authorization grant type. "invalid_client"|"invalid_client"|Client authentication failed. "unsupported_grant_type"|"unsupported_grant_type"|The authorization server does not support the authorization grant type. "invalid_resource"|"invalid_resource"|The target resource is invalid because it does not exist, Azure AD can't find it, or it's not correctly configured. "interaction_required"|"interaction_required"|The request requires user interaction. For example, an additional authentication step is required. "temporarily_unavailable"|"temporarily_unavailable"|The server is temporarily too busy to handle the request.

VIDPTokenValidationErrorCodes

Property|Type|Description -|-|- "malfomed_token"|"malfomed_token"|The token is malformed.It may not consist of three segments or may not be parseable by the jsonwebptoken library. "missing_header"|"missing_header"|The token is malformed. Its header is missing. "missing_payload"|"missing_payload"|The token is malformed. Its payload is missing. "missing_signature"|"missing_signature"|The token is malformed. Its signature "no_such_public_key"|"no_such_public_key"|The token requested a public key with an id that does not exist in the metadata endpoint. "verification_error"|"verification_error"|An error occured when verifying the token against nonce, clientId, issuer, tolerance or public key. "incorrect_hash"|"incorrect_hash"|The token did not match the expected hash

VIDPStrategyErrorCodes

Property|Type|Description -|-|- "missing_required_setting"|"missing_required_setting"|A required setting was missing. See description for more information. "invalid_internal_state"|"invalid_internal_state"|The internal state of the system is not valid. This may occur when users peforms authentication too slowlyor if an attacker is attempting a replay attack. "verifier_error"|"verifier_error"|An error occured in the verifier function called once the authentication is completed. "unknown_error"|"unknown_error"|This error code occurs if the system was unable to determine the reason for the error.Check the error details or innerError for more information.

VIDPRefreshTokenErrorCodes

Property|Type|Description -|-|- "cannot_resolve_token"|"cannot_resolve_token"|Token refresh middleware was unable to resolve the token using the provided resolver.See description for more details.

Helpers

Encrypting session

When configuring authentication you need to provide a place to store session data. This is done through the store configuration for express-session. In the samples we use a MemoryStore instance that keeps the data in memory, but this is not suitable to for production as it does not scale. For such systems you would probably go with a database or cache of some kind such as MySQL or Redis.

Once you set up such a session storage mechanism, however there are some considerations you need to take into account. Since the access tokens for individual users are stored as session data it means that anyone with access to the session storage database can extract any token for a currently logged in user and use it themselves. Since the token is the only key needed to perform actions on behalf of the user it is considered sensitive information and must therefore be protected accordingly.

This library comes with a helper function to deal with just this scenario called createEncryptedSessionStore. This function uses the AES-256-CBC algorithm to encrypt and decrypt a subset of session data on-the-fly preventing someone with access to the store from seeing the plain access tokens. They will only see an encrypted blob of text.

The way createEncryptedSessionStore works is that it replaces the read and write functions of an express-session compatible store with augmented versions that decrypt and encrypt a set of specified properties (if present on the session object) respectively. This means that you can still use any of the compatible store connectors and simply pass it through the helper function to get a version that provides encryption.

Using the Redis connector you can configure an encrypted session like this:

const session = require("express-session")
const { createEncryptedSessionStore } = require("@veracity/node-auth")
const redisStore = require("connect-redis")(session)

// You should NOT hard-code the encryption key. It should be served from a secure store such as Azure KeyVault or similar
const encryptedRedisStore = createEncryptedSessionStore("encryption key")(redisStore)

// We can now use the encryptedRedisStore in place of a regular store to configure authentication
setupWebAppAuth({
	app,
	strategy: {
		clientId: "",
		clientSecret: "",
		replyUrl: ""
	},
	session: {
		secret: "ce4dd9d9-cac3-4728-a7d7-d3e6157a06d9",
		store: encryptedRedisStore // Use encrypted version of redis store
	}
})

Generate certificate

A helper to generate certificate that can be used for local development.

const express = require("express")
const app = express()
const https = require("https")
const { generateCertificate } = require("@veracity/node-auth")

app.get("/", (req, res, next) => {
	res.send("Frontpage here")
})

// Set up the HTTPS development server
const server = https.createServer({
	...generateCertificate() // Generate self-signed certificates for development
}, app)

server.on("error", (error) => { // If an error occurs halt the application
	console.error(error)
	process.exit(1)
})

server.listen(3000, () => { // Begin listening for connections
	console.log("Listening for connections on https://localhost:3000/")
})