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

@fewlines/connect-client

v0.5.3

Published

OAuth2 Client for the Connect JS SDK

Downloads

15

Readme

Fewlines Connect-js Client

Disclaimer: this package is made for our internal usage and is only open source for convenience so we might not consider Pull Requests or Issues. Feel free to fork though.

Client is part of the Fewlines Connect-js SDK.

It mainly provides an abstraction for the OAuth2 flow, and a secure way to verify JWT for HS256 and RS256 algorithms (signed and/or encrypted).

Installation

yarn add @fewlines/connect-client

Getting Started

You first need to initialize the client instance, called OAuth2Client. This class takes the following constructor parameters:

  • openIDConfigurationURL: The URL to retrieve the OpenID configuration.
  • clientID: Client ID of the online service (e.g. internet website, application) that uses the Provider Authentication and Authorization service for its User.
  • clientSecret: Paired with the client ID, used to authenticate the Application from which the User intent to sign in.
  • redirectURI: URI used to redirect to the original Application website after a successful login in on Connect.
  • audience: The audience of the Application (its name for Connect).
  • scopes: It represents the kind of information and actions that an Application is able to access on another Application. Every scope has to be authorized by the User during sign in operation.
  • openIDConfiguration (optional): Provides a way to initialize directly an openID configuration, without fetching the OpenID endpoint.
import OAuth2Client, {
  OAuth2ClientConstructor,
} from "@fewlines/connect-client";

const oauthClientConstructorProps: OAuth2ClientConstructor = {
  openIDConfigurationURL: "***",
  clientID: "***",
  clientSecret: "***",
  redirectURI: "***",
  audience: "***",
  scopes: ["***", "***"],
};

const oauthClient = new OAuth2Client(oauthClientConstructorProps);

Usage

The initialized instance of OAuth2Client provides the following methods to help with the OAuth2 flow. Each method will initialize the open OpenID configuration returned from the URL provided if not initialized yet.

getAuthorizationURL

async getAuthorizationURL(state?: string): Promise<URL>{}

The OAuth2 flow requires to move out of your application. If you need to keep some sort of state (like a user ID), you can pass said state to the function, which will be be added at the end of the query string. The function returns the authorization URL used to start the OAuth2 flow, and retrieve the authorization_code.

const authURL = await oauthClient.getAuthorizationURL("state");

getTokensFromAuthorizationCode

async getTokensFromAuthorizationCode(authorizationCode: string): Promise<string[]>{};

Returns a list containing the access_token, refresh_token, and id_token if present.

const tokens = await oauthClient.getTokensFromAuthorizationCode(
  "authorization_code",
);

verifyJWT

async verifyJWT<T = unknown>(accessToken: string, algo: string, audience?: string): Promise<T> {}

Used to verify the JWS (i.e. access_token). It provides a series of checks, like audiences, algorithm or public key. The audience parameter is optional and will default to the value you provided when creating the client.

const decoded = await oauthClient.verifyJWT(JWS, "RS256");

decryptJWE

async decryptJWE<T = unknown>(JWE: string, privateKey: string, isSigned: boolean): Promise<T> {}

Used to decrypt the JWE (i.e. access_token), and returns the JWS or the JWT.

The privateKey argument should be in PEM format.

// Signed JWE
const decrypted = oauthClient.decryptJWE<string>(JWE, privateKey, true);

// Un-signed JWE
const decrypted = oauthClient.decryptJWE<{ [key: string]: string }>(
  JWE,
  privateKey,
  false,
);

refreshTokens

async refreshTokens(refresh_token: string): Promise<{refresh_token: string, access_token: string}>

Returns a refreshed access_token along with a new refresh_token.

const { refresh_token, access_token } = await oauthClient.refreshTokens(
  refresh_token,
);

getUserInfo

_ This method is still a work in progress _

async getUserInfo<T = Record<string, unknown>>(accessToken: string): Promise<T>

Returns the JSON response from the Provider's /userinfo endpoint fetching, concerning the user associated with the access_token provided in parameter.

const userInfoResponse = await oauthClient.getUserInfo<{
  email: string;
  phone_number: string;
}>(accessToken);

Utils

@fewlines/connect-client also provides utils functions and default objects that we are using in the package flow.

generateHS256JWS

import { generateHS256JWS } from "@fewlines/connect-client"

generateHS256JWS(customPayload?: CustomPayload, secret?: string): string {};

If used without any argument, the function will return a default HS256 JWS composed of the default objects found below.

You can give a custom secret for signature, and/or a custom payload to customize your HS256 JWS.

generateRS256JWS

import { generateRS256JWS } from "@fewlines/connect-client"

generateRS256JWS(customPayload?: CustomPayload, secret?: string): string {};

If used without any argument, the function will return a default RS256 JWS composed of the default objects found below.

You can give a custom secret for signature, and/or a custom payload to customize your RS256 JWS.

generateJWE

import { generateJWE } from "@fewlines/connect-client"

await generateJWE(
  JWTPayload: JWTPayload,
  publicKeyForEncryption: string,
  options?: { secretKey?: string; privateKeyForSignature?: string }
): Promise<string> {};

This function takes as arguments a JWT payload, a RSA public key for encryption, and an optional object, options, containing either a secret key for signing a HS256 JWS, or a RSA private key for signing a RS256 JWS before encrypting it. It will then return a JWE based on a non-signed JWT or a JWS, according to arguments provided.

default JWS composition objects

The following exported objects are used when generating a JWS using generateHS256JWS and generateHS256JWS, while no arguments are passed:

const defaultPayload: JWTPayload = {
  aud: ["oauth2"],
  exp: Date.now() + 3600,
  iat: Date.now(),
  iss: "https://fwl.test",
  scope: "email",
  sub: "2a14bdd2-3628-4912-a76e-fd514b5c27a8",
};
const defaultAsymmetricAlgoKeyPair = {
  privateKey: `-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAsBUV0XnPsTCYhI3TmcxjSmnOmlQlKWfFbr/sABcM65P6f28g
c39XyWj/CcE+iIscrfvodinyHr2dXm+wpTnVyqZUsOavEHhq0n8flp8G4RdsYDMn
MIH6gat4XVTLztLiM0Vsb+h5LAD1ETxiVNtoB3urtD9Wn6F1NobDWc1FKtrGGCyt
HC0S7GNVAUE+sZTxPTXOraCV8aZt5McPnZE7V2ohSoJJR4HxUV16xIlOkxDKrvt1
INTjE4G3va0kF+NO37as696KLnylS/Zk13zS0+GKU+swjJHMHGTwy8p4ZRdW+94k
Q8jQjkc3QNswv7GDqy+om5S36qz2UfEudDg8vwIDAQABAoIBAQCblcX1liCCfGIH
nhn+p66ELgG0M+2yLFMzjLnHSpbMAbExsMuHwlmbSf8wa2QJ2oG46gF5h468wuau
bxoypGvX6CilNvAxKXWaEWGkAVhexAeq6QgTwIXM1epA7ZA4a/YbP5iVOWD4FNB2
LljkigndstUBK4Yq/stHJsSuMMaRGc6p7SwGe/qbJLFE2gU13umQr0eGJGcunoaa
/NI8pI+XUqNBJ5o0gHwPVW39dCtN2PCfV+pnh4d/0FcGKJMNbZLutgmwyo+dB2+R
2iJPxjam/fZ0eaoGd0b9IaVCgrLb4er+9NW6P7C3SmUGk0FynsG0p7SZdyIFhT1M
2RLjX+1BAoGBAN2yQqkSr3Tv9dkf/R237RowAgTCAsuvwwztQSCgaOQUVxzaiCaN
boJHRCSjz3LF6e2678T6TUnJyeTPnYGBt7/5Abxhu3kpxMZdn433YszdBoFucs0Z
Vs/BPX0T5EP18FEE5QpHewFBEn7hCD/ITc/ESuFpzczBujtTt4XHgSrZAoGBAMtT
++uVK6IFjikiIZ6mfasFql8bgLsN20rJEmdVnUdrGWhClt1ofcc1c58BXTZ9XTFW
HUr4NXJjRWIeG7CQr4JBpbZrhrWrMTpmj1YmmjLAJBht3EUa6iY6ZugSwX0Hs8BX
jz8o5Q8W55ovTMvXSyOpmO3hUc4M7Gw5B3UXOfVXAoGBAMr/FAzOYDx6EKo8oT9a
D9Afz2ld4MNzb+1hPXZLi1/0xANWkr6CWIMQEHRgTm6wjE+zESNTcfuzups6A+X2
yjMNJarB7rC2L0jXp2aN9DjT1cYkRRhKMHRRZCUoVqZoByGYksTDyPzQAciN80i7
94vcGBipEdmANi9mq6/iiwKZAoGBAJDzmXfUS1vhO/ylAXS52oMOsevFjptLgJcr
Czyxs0NT3bQPLMBBGtHmkDu93DbZXpOqgY2Nr4SRssgFENZp/0UMCdgnhcyDc7/f
l7XiJTGi+tiTkWGOk3iXT/+IR3ocAJBRm6R6Qfnk6U1pBQWYwU92O7jyVcBgRDfO
3DskSMRXAoGAYMK81hY9A+XF6Y77/cz5fYn75/T29+7bgHCNdN5Ky91EH6KWsBpx
ayXuC8YrChmmGRB09QhOtS1P0HYYzfXFaEdBVltjuBRXYQqLAlolx4vchpTYBpsu
EvQSc3gTNTqK91gATPZ+5uN7+cdbiMfc5hGKTeiMm6F9m95phyPMS1c=
-----END RSA PRIVATE KEY-----`,
  publicKey: `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsBUV0XnPsTCYhI3Tmcxj
SmnOmlQlKWfFbr/sABcM65P6f28gc39XyWj/CcE+iIscrfvodinyHr2dXm+wpTnV
yqZUsOavEHhq0n8flp8G4RdsYDMnMIH6gat4XVTLztLiM0Vsb+h5LAD1ETxiVNto
B3urtD9Wn6F1NobDWc1FKtrGGCytHC0S7GNVAUE+sZTxPTXOraCV8aZt5McPnZE7
V2ohSoJJR4HxUV16xIlOkxDKrvt1INTjE4G3va0kF+NO37as696KLnylS/Zk13zS
0+GKU+swjJHMHGTwy8p4ZRdW+94kQ8jQjkc3QNswv7GDqy+om5S36qz2UfEudDg8
vwIDAQAB
-----END PUBLIC KEY-----`,
};
const defaultSecret = "c9ab0fdc-b2dc-47ad-933b-87cf1b180ab5";

They are all imported like so:

import {
  defaultAsymmetricAlgoKeyPair,
  defaultPayload,
  defaultSecret,
} from "@fewlines/connect-client";

decodeJWTPart

Takes a part from a JWT (e.g. the JWA, or the payload), and decode it.

import { decodeJWTPart } from "@fewlines/connect-client";

const [JWA, payload] = JWT.split(".");

const decodedJWA = decodeJWTPart(JWA);
const decodedPayload = decodeJWTPart(payload);

rsaPublicKeyToPEM

Takes a modulus and an exponent (found in the JWKS), and recreate a public key.

import { rsaPublicKeyToPEM } from "@fewlines/connect-client";

const key = {
  e: "AQAB",
  kty: "RSA",
  kid: "d6512f53-9774-4a58-830c-981886c8bb43",
  n: "y3M7JqY49JeL/ornP7ZY2QlO76akS36Rj1iKVSIlFH754NnqmtGwMrCVZzCWrc882trbGuDhml2psOmCIBjKBpnghNLBZALGNRelCqfV7Cy+EMrQvQ+UWbogT7xfPoL+VYjCZKTeXosfzMNMZFum/Vnk/vYBKilXZfQH1t4sohU=",
  alg: "RS256",
};

const { e, n } = key;
const publicKey = rsaPublicKeyToPEM(n, e);

generateRSAKeyPair

This function will generate and return a RSA public and private key pair in PEM format.

import { generateRSAKeyPair } from "@fewlines/connect-client";

const { publicKey, privateKey } = generateRSAKeyPair();

Note that we can also run this command in your terminal to generate a RSA public/private key pair in PEM format :

openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem

Exceptions

Here is an overview of the exceptions raised from the library:

  • MissingJWKSURIError
  • InvalidKeyIDRS256Error
  • MissingKeyIDHS256Error
  • AlgoNotSupportedError
  • InvalidAudienceError
  • ScopesNotSupportedError
  • UnreachableError

In case of an issue with fetching the OpenID endpoints, the library will throw a UnreachableError, based on FetchError.