Skip to content

therootcompany/libauth-oidc-google.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@libauth/oidc-google

OIDC for Google Sign In for LibAuth.js

Specifically, this is for https://accounts.google.com

Issuer https://accounts.google.com/
Discovery URI https://accounts.google.com/.well-known/openid-configuration
JWKs URI https://www.googleapis.com/oauth2/v3/certs

See also: https://therootcompany.com/blog/google-sign-in-javascript-api/

Install

npm install --save libauth @libauth/oidc-google

Usage

You must first create a set of credentials with an approved redirect_uri in the Google Cloud Dashboard:

Protecting your Google Credentials

Don't commit your Google Sign In credentials to code.

Rather use a .env for local development and servers, or the Environment Configuration (or Secrets Vault) of your CI/CD service.

.env:

# Found at https://console.developers.google.com/apis/dashboard
GOOGLE_CLIENT_ID='00...00-XX...XX.apps.googleusercontent.com'
GOOGLE_CLIENT_SECRET='XXXXXXXXXXXXXXXXXXXXXXXX'
require("dotenv").config({ path: ".env" });

Example with Express

The goal of LibAuth is

  • 🚫 🪄 to minimize magic (anything difficult to understand or configure)
  • 👍 🎮 and maximize control
    without sacrificing
  • ✅ 🏪 ease-of-use or convenience

To do this we require more ✂️ 📋 copy-and-paste boilerplate than other auth libraries - with the upside is that it's all just normal, easy-to-replace 🥞 middleware - hopefully nothing 🤔 unexpected or ⛓ constraining.

// GOOGLE SIGN IN

let googleOidc = libauth.oidc(
  require("@libauth/oidc-google")({
    clientId: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    redirectUri: "/api/session/oidc/accounts.google.com/code",
  }),
);

//
// For 'Authorization Code' (Server-Side Redirects) Flow
// (requires `clientId` and `clientSecret`)
//
app.get(
  "/api/authn/oidc/accounts.google.com/auth",
  googleOidc.setAuthUrl,
  googleOidc.redirectToAuthUrl,
);
app.get(
  "/api/session/oidc/accounts.google.com/code",
  googleOidc.exchangeCode,
  googleOidc.verifyToken,
  MyDB.getUserClaimsByOidcEmail,
  libauth.newSession(),
  libauth.setClaims(),
  libauth.setTokens(),
  libauth.setCookie(),
  MyDB.updateSessionId,
  libauth.setCookieHeader(),
  libauth.redirectWithTokens("/my-account"),
);

//
// For 'Implicit Grant' (Client-Side) Flow
// (requires `clientId` only)
//
app.post(
  "/api/session/oidc/accounts.google.com/token",
  googleOidc.verifyToken,
  MyDB.getUserClaimsByOidcEmail,
  libauth.newSession(),
  libauth.setClaims(),
  libauth.setCookie(),
  libauth.setTokens(),
  MyDB.updateSessionId,
  libauth.setCookieHeader(),
  libauth.sendTokens(),
);

User Middleware

The things that LibAuth can't do for you:

  1. Get your user from your database
  2. Decide what details about the user (claims) to include in the token
  3. Invalidate a user's device in your database

Claims is a standard term meaning the standard (or private or custom) properties of a token which describe the user.

The list of Standard OIDC Claims for ID Tokens: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims

MyDB.getUserClaimsByOidcEmail = function (req, res, next) {
  async function mw() {
    // get a new session
    let email = libauth.get(req, "oidc").email;
    let user = await DB.User.get({ email: email });

    // "claims" is the standard term for "user info",
    // and includes pre-defined values such as:
    let idClaims = {
      // "Subject" the user ID or Pairwise ID (required)
      sub: user.id,

      // ID Token Info (optional)
      given_name: user.first_name,
      family_name: user.first_name,
      picture: user.photo_url,
      email: user.email,
      email_verified: user.email_verified_at || false,
      zoneinfo: user.timezoneName,
      locale: user.localeName,
    };

    let accessClaims = {
      // "Subject" the user ID or Pairwise ID (required)
      sub: user.id,
    };

    libauth.set(req, { idClaims: claims, accessClaims: accessClaims });

    next();
  }

  // (shim for adding await support to express)
  Promise.resolve().then(mw).catch(next);
};

MyDB.updateSessionId = function (req, res, next) {
  async function mw() {
    // Invalidate the old session, if any
    let sessionId = libauth.get(req, "currentSessionClaims")?.jti;
    if (sessionId) {
      await DB.Session.set({ id: sessionId, deleted_at: new Date() });
    }

    // Save the new session
    let newSessionClaims = libauth.get(req, "sessionClaims");
    let newSessionId = newSessionClaims.jti;
    let userId = newSessionClaims.sub;

    await DB.Session.set({ id: newSessionId, user_id: userId });

    next();
  }

  // (shim for adding await support to express)
  Promise.resolve().then(mw).catch(next);
};

Some claims will be added for you unless provided or set to false:

Claim Description
iss Issuer (where public keys can be found)
iat Issued At (defaults to current time)
jti JWT ID (used for tracking session)
exp Expiration (ex: '15m' or '2h')
auth_time The original time of authentication

Unless otherwise defined, sessionClaims and refreshClaims will be computed to contain the same sub, iss, iat, aud, auth_time, azp, and jti` as the computed idClaims, after the above claims are added.

Note: In libauth jti is expected to be used to invalidate Refresh Tokens and associated ID and Access Tokens before their given Expiration.

Developing

Rather than a monorepo, we've chosen the git submodule approach (to keep git tags distinct, etc).

git clone https://github.com/therootcompany/libauth.js
pushd ./libauth.js/
git submodule init
git submodule update
pushd ./plugins/oidc-google/
git checkout main

About

OIDC for Google Sign In for LibAuth.js

Resources

License

Stars

Watchers

Forks

Packages

No packages published