@webqit/webflo-oauth2-client

1.1.0 • Public • Published

Webflo OAuth2 Client

NPM version NPM downloads

Webflo OAuth2 library for Node.js.

Installation

npm i @webqit/webflo-oauth2-client

Usage

import { ConsentFlow } from '@webqit/webflo-oauth2-client';

Initialization

const createConsentFlow = ( httpEvent, fetch ) => {

    // Client app config
    const client = {
        // The application's base URL. E.g. http://localhost:3000/ (local), https://example.com/ (production)
        baseUrl: process.env.OAUTH2_CALLBACK_HOST,
        // Application's auth-flow endpoints, relative to base URL
        callbacks: {
            // The route where you'll handle signed-in callback from provider screen. (See below: The "Signed-In" Callback Route)
            signedIn: '/auth',
            // The route where you'll handle signed-out callback from provider screen. (See below: A "Sign Out" Route)
            signedOut: '/',
        },
    };

    // Provider: auth0
    const auth0 = {
        // Auth0-issued client ID
        clientId: process.env.AUTH0_CLIENT_ID,
        // Auth0-issued client secret
        clientSecret: process.env.AUTH0_CLIENT_SECRET,
        // OAuth2 server base URL. E.g. https://example.us.auth0.com/
        baseUrl: process.env.AUTH0_BASE_URL,
        // Auth0-issued auth-flow endpoints, relative to base URL
        endpoints: {
            signIn: '/authorize',
            token: '/oauth/token',
            revoke: undefined,
            signOut: '/v2/logout',
        },
        // Parameters for verifying Auth0-issued ID Tokens
        jwtokens: { jwksUrl: '.well-known/jwks.json', }
    };

    // Provider: google
    const google = {
        // Google-issued client ID
        clientId: process.env.GOOGLE_CLIENT_ID,
        // Google-issued client secret
        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        // OAuth2 server base URL. Always https://oauth2.googleapis.com/ for Google
        baseUrl: process.env.GOOGLE_BASE_URL,
        // Google-issued auth-flow endpoints, relative to base URL
        endpoints: {
            signIn: 'https://accounts.google.com/o/oauth2/v2/auth',
            token: '/token',
            revoke: '/revoke',
            signOut: undefined,
        },
        // Parameters for verifying Google-issued ID Tokens
        jwtokens: { jwksUrl: 'https://www.googleapis.com/oauth2/v3/certs', }
    };

    // Auth instance
    return new ConsentFlow( httpEvent, {
        client, providers: { google/* as default provider */, auth0 },
    }, fetch );

};

Initialization Route

At the application's root handler, you'd initialize oauth2 client into the context object that is passed around:

export default function( httpEvent, context, next, fetch ) {
    context.oauth2 = createConsentFlow( httpEvent, fetch );
    if ( next.pathname ) return next( context );
    return { titleBar: 'Home' };
}

A Protected "Sign In" Route

To protect a route, you'd call the signIn() method with optional fields - { provider /* defaults to the name of the first provider in list */, scope, audience }:

export default function( httpEvent, context, next, fetch ) {
    return context.oauth2.signIn( { /* optional */scope: 'openid' }, session => {
        // Authenticated...
        // otherwise this function is not called, and user is redirected to provider sign in screen
        // session is same as context.oauth2.session

        // Are we going to a protected child page?
        // session can always be accessed as context.oauth2.session down the hierarchy
        if ( next.pathname ) return next( context );

        // See what's in auth session
        console.log( session ); // { access_token, token_type: 'Bearer', scope, provider, info, ...etc }
        // See what's in session.info
        console.log( session.info ); // { iss, sub: <user ID>, exp, ...etc }
        // (If "profile", "email" were in the value of the "scope" parameter (as an array or a space-delimitted string), other infos would be available: email, name, avatar_url, etc.)

        // Call an API endpoint at provider
        const provider = context.oauth2.client.provider( /* specify provider name, otherwise default provider is implied */ );
        const endpoint = '/userinfo'; // Or if defined in provider settings object above: endpoint = provider.endpoints.userinfo
        const user = await provider.call( session.access_token, endpoint, { ...optionalBody } );

        /*
        Or if you wish...
        const user = await fetch( 'https://oauth2.example.com/endpoint', {
            body: JSON.stringify( { ...optionalBody } ),
            headers: { 'Content-Type': 'application/json', Authorization: session.access_token }
        } ).then( res => res.json() );
        */

        // Show accounts page
        return {
            titleBar: 'My Account',
            email: user.email,
        }
    } );
}
The context.oauth2.signIn() function could also be called without a callback...
export default function( httpEvent, context, next, fetch ) {
    let session, redirect, sessionOrRedirect = context.oauth2.signIn( { /* optional */scope: 'openid' } );
    if ( sessionOrRedirect instanceof httpEvent.Response ) {
        // User is being redirected to provider sign in screen
        return ( redirect = sessionOrRedirect /* formality assignment */ );
    }
    // Authenticated...
    session = sessionOrRedirect;
    // session is same as context.oauth2.session
    
    // Are we going to a protected child page?
    // session can always be accessed as context.oauth2.session down the hierarchy
    if ( next.pathname ) return next( context );

    // See what's in auth session
    console.log( session ); // { access_token, token_type: 'Bearer', scope, provider, info, ...etc }
    // See what's in session.info
    console.log( session.info ); // { iss, sub: <user ID>, exp, ...etc }
    // (If "profile", "email" were in the value of the "scope" parameter (as an array or a space-delimitted string), other infos would be available: email, name, avatar_url, etc.)

    // Call an API endpoint at provider
    const provider = context.oauth2.client.provider( /* specify provider name, otherwise default provider is implied */ );
    const endpoint = '/userinfo'; // Or if defined in provider settings object above: endpoint = provider.endpoints.userinfo
    const user = await provider.call( session.access_token, endpoint, { ...optionalBody } );

    /*
    Or if you wish...
    const user = await fetch( 'https://oauth2.example.com/endpoint', {
        body: JSON.stringify( { ...optionalBody } ),
        headers: { 'Content-Type': 'application/json', Authorization: session.access_token }
    } ).then( res => res.json() );
    */

    // Show accounts page
    return {
        titleBar: 'My Account',
        email: user.email,
    }
}

The "Signed In" Callback Route

To handle the "signed-in" redirect from provider screen - at the specified callback URL client.callbacks.signedIn:

export default function( httpEvent, context, next ) {
    if ( context.oauth2.isSigningIn() /* Detects if a sign-in session is ongoing */ ) {
        // Performs "authorization_code" grant and redirects user back to the original protected route - where signIn() was called
        return context.oauth2.handleSignInCallback();
    }
    // Returns the context.oauth2.session object or false
    return context.oauth2.isSignedIn()?.info || {};
}

A "Sign Out" Route

To perform "sign out" at any route:

export default function( httpEvent, context, next ) {
    if ( context.oauth2.isSignedIn() ) {
        // User is signed-out (and is redirected to provider's sign-out URL, where given),
        // then redirected back to "/" as specified in client.callbacks.signedOut
        return context.oauth2.signOut();
    }
    return next();
}

Full Documentation

Full documentation, including integrating other providers, coming soon.

Issues

To report bugs or request features, please submit an issue to this repository.

License

MIT.

Package Sidebar

Install

npm i @webqit/webflo-oauth2-client

Weekly Downloads

0

Version

1.1.0

License

MIT

Unpacked Size

36.3 kB

Total Files

10

Last publish

Collaborators

  • ox-harris