1.0.0 • Published 10 days ago

express-authenticators v1.0.0

Weekly downloads
25
License
MIT
Repository
-
Last release
10 days ago

Express Authenticators Build Status

NPM

Modern OAuth/OAuth2 authenticator.

Features

  • Pre-configured for popular providers: Apple, Google, Facebook, Foursquare, Github, Twitter, LinkedIn, LINE, Pinterest, Tumblr, Instagram.
  • Pre-configured for popular scopes: email, profile, etc. with account fetching for basic user information.
  • The original OAuth/OAuth2 classes are available for customized providers.
  • The only dependencies are r3986.
  • Modern NodeJS. Although, it requires NodeJS >= v14.17.0 to use the randomUUID() function.
  • Strongly typed with TypeScript.
  • Support PKCE(Proof Key for Code Exchange).
  • Generic and pure interface. Do not depend on any framework.

Usage

  • With yarn: yarn add express-authenticators.
  • With npm: npm install --save express-authenticators.

Requirement

  • fetch polyfilled.
  • NodeJS >= v14.17.0.

(before v0.1.0, this package was for ExpressJS only, hence its name is express-authenticators)

Sample code in ExpressJS

const {
	AppleAuthenticator,
	FacebookAuthenticator,
	FoursquareAuthenticator,
	GithubAuthenticator,
	GoogleAuthenticator,
	LineAuthenticator,
	InstagramAuthenticator,
	LinkedInAuthenticator,
	PinterestAuthenticator,
	TumblrAuthenticator,
	TwitterAuthenticator,
	ZaloAuthenticator,
	OAuth2,
	OAuth
} = require('express-authenticators')
const express = require('express')
const session = require('express-session')

const app = express()
app.use(session())

const facebookAuth = new FacebookAuthenticator({
	clientID: 'facebook app id',
	clientSecret: 'facebook app secret',
	redirectUri: `https://example.com/auth/facebook/callback`,
})

app.get(
	'/auth/facebook',
	async (req, res, next) => {
		req.session.someInfo = 'my info' // store the user credential
		try {
			const redirectUrl = await facebookAuth.authenticate({
				store(token) {
					req.session.oauthFacebook = token
				}
			})
			res.status = 302
			res.redirect(redirectUrl)
		} catch (e) {
			next(e)
		}
	}
)
app.get( // for AppleAuthenticator, must use POST method instead
	`/auth/facebook/callback`,
	async (req, res, next) => {
		try {
			const payload = await facebookAuth.callback(
				req.session.oauthFacebook,
				new URL(`https://example.com${req.url}`).search // for AppleAuthenticator, use req.body instead
			)
			const profile = await facebookAuth.fetchProfile(payload) // not supported by AppleAuthenticator
			console.log('got profile', profile)
			res.send(JSON.stringify(profile))
		} catch (e) {
			next(e)
		}
	}
)

API references

Exported classes

  • 2 generic classes: OAuth2 and OAuth.

  • Pre-configured providers that inherit OAuth: TwitterAuthenticator, TumblrAuthenticator.

  • Pre-configured providers that inherit OAuth2:
    • AppleAuthenticator
    • FacebookAuthenticator
    • FoursquareAuthenticator
    • GithubAuthenticator
    • GoogleAuthenticator
    • InstagramAuthenticator
    • LinkedInAuthenticator
    • PinterestAuthenticator
    • LineAuthenticator
    • ZaloAuthenticator

Constructors

  • All pre-configured providers' constructors take only one parameter: options with the following properties.
{
	clientID: string
	clientSecret: string // not required for AppleAuthenticator
	redirectUri: string
}

Most generic methods

All exported classes inherit the IOAuthCommon interface which has the following methods:

  • authenticate(session: {store(token: string): void | Promise<void>}): string | Promise<string>.

    • Input: this method takes only one argument, session whose store method is called with a token in string type to store in the request session. This data will be required in the succeeding callback() method.
    • Output: redirect url. The controller/router should redirect the user to this url. This function always returns a string type or throws an error if it fails.
  • callback({pop}: {pop(): string | undefined}, rawQuery: string):

    • Input: pop is a function that returns the token from the request session. This token is required to validate the authentication.
    • Input: rawQuery is the query string from the callback url, the query may or may not contain the leading ? character (internally, we use URLSearchParams which handles this automatically).
    • Output: the token payload returned from the provider. For OAuth providers, this is {token: string, secret: string}. For OAuth2 providers, the payload is the JSON-parsed response from the provider which usually contains the token for further request.

Pre-configured providers' methods

Pre-configured providers have the following methods:

  • fetchProfile(tokenPayload): Promise<IOAuthProfile> (not available with AppleAuthenticator): takes the token payload returned from the callback() method and returns the profile data. Although each provider returns different data, they are all pre-configured in this library to return the IOAuthProfile described below.
export interface IOAuthProfile {
	id?: string
	email?: string
	emailVerified?: boolean
	first?: string
	last?: string
	avatar?: string
	raw: any
}

Where raw is the raw JSON-parsed data returned from the provider. Other fields are calculated carefully based on the data returned from the provider.

Customized provider

While I recommend you using the pre-configured providers, you can also create your own customized provider by extending the OAuth/OAuth2 classes or initialize a new instance of the OAuth/OAuth2 classes directly.

Here are two sample implementations of FacebookAuthenticator (extending OAuth2), and TwitterAuthenticator ( extending OAuth)

class FacebookAuthenticator
	extends OAuth2<IFacebookTokenPayload>
	implements IOAuthProfileFetcher<IFacebookTokenPayload> {
	fetchProfile = fetchFacebookProfile

	constructor(options: {
		clientID: string
		clientSecret: string
		redirectUri: string
		scope?: string
	}) {
		super({
			consentURL: 'https://www.facebook.com/v9.0/dialog/oauth',
			tokenURL: 'https://graph.facebook.com/v9.0/oauth/access_token',
			scope: ['email'].join(','),
			...options,
		}, {
			ignoreGrantType: true,
			tokenRequestMethod: TokenRequestMethod.GET,
			includeStateInAccessToken: false,
			enablePKCE: false,
		})
	}
}

export default class TwitterAuthenticator extends OAuth implements IOAuthProfileFetcher<IOAuthTokenPayload> {
	constructor(config: {
		clientID: string
		clientSecret: string
		redirectUri: string
	}) {
		super({
			consumerKey: config.clientID,
			consumerSecret: config.clientSecret,
			callbackUrl: config.redirectUri,
			requestTokenUrl: 'https://api.twitter.com/oauth/request_token',
			accessTokenUrl: 'https://api.twitter.com/oauth/access_token',
			authorizeUrl: 'https://api.twitter.com/oauth/authorize',
			signingMethod: OAuthSigningMethod.Hmac,
		})
	}

	async fetchProfile(tokenPayload: IOAuthTokenPayload) {
		const response = await this.signAndFetch(
			'https://api.twitter.com/1.1/account/verify_credentials.json',
			{
				qs: {include_email: true},
			},
			tokenPayload
		)
		if (!response.ok) throw new OAuthProfileError(await response.text())
		const profile = await response.json()
		if (!profile.id_str) throw new OAuthProfileError('Invalid Twitter profile ID')
		return {
			id: profile.id_str,
			raw: profile,
			avatar: profile.profile_image_url_https
				|| profile.profile_image_url
				|| profile.profile_background_image_url_https
				|| profile.profile_background_image_url,
			first: profile.name || profile.screen_name,
			email: profile.email,
			emailVerified: !!profile.email,
			/**
			 * from twitter docs
			 * https://developer.twitter.com/en/docs/accounts-and-users
			 * /manage-account-settings/api-reference/get-account-verify_credentials
			 * When set to true email will be returned in the user objects as a string.
			 * If the user does not have an email address on their account,
			 * or if the email address is not verified, null will be returned.
			 */
		}
	}
}
1.0.0

10 days ago

1.0.0-pre-1

10 months ago

1.0.0-pre-5

10 months ago

1.0.0-pre-4

10 months ago

1.0.0-pre-3

10 months ago

1.0.0-pre-2

10 months ago

0.2.1-beta1

11 months ago

0.2.0-experiiment-4

11 months ago

0.2.0-experiiment-5

11 months ago

0.2.0-experiiment-2

11 months ago

0.2.0-experiiment-3

11 months ago

0.2.0-experiiment-1

11 months ago

0.2.1

11 months ago

0.2.0

11 months ago

0.1.4

11 months ago

0.1.5

11 months ago

0.1.3

1 year ago

0.1.0

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.0.10

3 years ago

0.0.11

3 years ago

0.0.9

3 years ago

0.0.8

3 years ago

0.0.7

4 years ago

0.0.6

4 years ago

0.0.5

4 years ago

0.0.4

4 years ago

0.0.3

4 years ago

0.0.2

4 years ago

0.0.1

4 years ago