Git Product home page Git Product logo

web-auth-library's Introduction

Web Auth Library

NPM Version NPM Downloads TypeScript Donate Discord

Authentication library for Google Cloud, Firebase, and other cloud providers that uses standard Web Crypto API and runs in different environments and runtimes, including but not limited to:

It has minimum dependencies, small bundle size, and optimized for speed and performance.

Getting Stated

# Install using NPM
$ npm install web-auth-library --save

# Install using Yarn
$ yarn add web-auth-library

Usage Examples

Verify the user ID Token issued by Google or Firebase

NOTE: The credentials argument in the examples below is expected to be a serialized JSON string of a Google Cloud service account key, apiKey is Google Cloud API Key (Firebase API Key), and projectId is a Google Cloud project ID.

import { verifyIdToken } from "web-auth-library/google";

const token = await verifyIdToken({
  idToken,
  credentials: env.GOOGLE_CLOUD_CREDENTIALS,
});

// => {
//   iss: 'https://securetoken.google.com/example',
//   aud: 'example',
//   auth_time: 1677525930,
//   user_id: 'temp',
//   sub: 'temp',
//   iat: 1677525930,
//   exp: 1677529530,
//   firebase: {}
// }

Create an access token for accessing Google Cloud APIs

import { getAccessToken } from "web-auth-library/google";

// Generate a short lived access token from the service account key credentials
const accessToken = await getAccessToken({
  credentials: env.GOOGLE_CLOUD_CREDENTIALS,
  scope: "https://www.googleapis.com/auth/cloud-platform",
});

// Make a request to one of the Google's APIs using that token
const res = await fetch(
  "https://cloudresourcemanager.googleapis.com/v1/projects",
  {
    headers: { Authorization: `Bearer ${accessToken}` },
  }
);

Create a custom ID token using Service Account credentials

import { getIdToken } from "web-auth-library/google";

const idToken = await getIdToken({
  credentials: env.GOOGLE_CLOUD_CREDENTIALS,
  audience: "https://example.com",
});

An alternative way passing credentials

Instead of passing credentials via options.credentials argument, you can also let the library pick up credentials from the list of environment variables using standard names such as GOOGLE_CLOUD_CREDENTIALS, GOOGLE_CLOUD_PROJECT, FIREBASE_API_KEY, for example:

import { verifyIdToken } from "web-auth-library/google";

const env = { GOOGLE_CLOUD_CREDENTIALS: "..." };
const token = await verifyIdToken({ idToken, env });

Optimize cache renewal background tasks

Pass the optional waitUntil(promise) function provided by the target runtime to optimize the way authentication tokens are being renewed in background. For example, using Cloudflare Workers and Hono.js:

import { Hono } from "hono";
import { verifyIdToken } from "web-auth-library/google";

const app = new Hono();

app.get("/", ({ env, executionCtx, json }) => {
  const idToken = await verifyIdToken({
    idToken: "...",
    waitUntil: executionCtx.waitUntil,
    env,
  });

  return json({ ... });
})

Backers 💰

              

Related Projects

How to Contribute

You're very welcome to create a PR or send me a message on Discord.

In order to unit test this library locally you will need Node.js v18+ with corepack enabled, a Google Cloud service account key (here) and Firebase API Key (here) that you can save into the test/test.override.env file, for example:

GOOGLE_CLOUD_PROJECT=example
GOOGLE_CLOUD_CREDENTIALS={"type":"service_account","project_id":"example",...}
FIREBASE_API_KEY=AIzaSyAZEmdfRWvEYgZpwm6EBLkYJf6ySIMF3Hy

Then run unit tests via yarn test [--watch].

License

Copyright © 2022-present Kriasoft. This source code is licensed under the MIT license found in the LICENSE file.


Made with ♥ by Konstantin Tarkus (@koistya, blog) and contributors.

web-auth-library's People

Contributors

koistya avatar unkhz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

web-auth-library's Issues

Firebase Emulator doesn't sign idToken, trips web-auth-library up

I'm using the Firebase Authentication Emulator, and kept getting this error locally from my CloudFlare Worker:

[FetchError: Public key "undefined" not found.]

After digging through the code a while and manually using jose to decode the idToken header (where the "undefined" comes from), I discovered that there is no kid on the locally generated idToken - more info here firebase/firebase-tools#2764

I could workaround this by using jose directly when in emulator mode, but it would be ideal if I could provide an 'emulator' flag in the options and the library would simply skip the signing check? A note about the emulator in the readme would also help future users.

eslint support

Hey, it seems eslint won't detect web-auth-library, meanwhile other modules works well.

my eslint config (simplified for the issue):

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'airbnb',
  ],
  plugins: [
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  rules: {
  },
};

image

As you may see in my screenshot, other modules works as expected .
And yes, I installed the module.
Here is a snap from may package-lock.json

...
    "node_modules/web-auth-library": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/web-auth-library/-/web-auth-library-1.0.3.tgz",
      "integrity": "sha512-fBmEjJSrbmbD9EREwVlewyfSVkb3IzgTXEF0fzXo3miDywsxES1vwG4aJGNpuSSUorZAGBJNyyz5VFq2VFgudw==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/kriasoft"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/koistya"
        }
      ],
      "dependencies": {
        "jose": ">= 4.12.0 < 5.0.0",
        "rfc4648": "^1.5.2"
      }
    },
...

Any clue?

Google Login GSI Support

I was hoping your library would support verifying a Google Login token as described here https://developers.google.com/identity/gsi/web/guides/verify-google-id-token

It was looking a different endpoint to verify Google's keys, and it was expecting a different audience.

I've modified your code and pieced it together to do just what I need. Feel free to incorporate these changes in your library in a way that doesn't break your other use cases:

import { decodeProtectedHeader, jwtVerify } from "jose";
import { importX509 } from "jose";
const inFlight = new Map();
const cache = new Map();

const canUseDefaultCache =
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
  typeof globalThis.caches?.default?.put === "function";

/**
 * Imports a public key for the provided Google Cloud (GCP)
 * service account credentials.
 *
 * @throws {FetchError} - If the X.509 certificate could not be fetched.
 */
async function importPublicKey(options) {
  const keyId = options.keyId;
  const certificateURL = options.certificateURL ?? "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"; // prettier-ignore
  const cacheKey = `${certificateURL}?key=${keyId}`;
  const value = cache.get(cacheKey);
  const now = Date.now();
  async function fetchKey() {
    // Fetch the public key from Google's servers
    const res = await fetch(certificateURL);
    if (!res.ok) {
      const error = await res
        .json()
        .then((data) => data.error.message)
        .catch(() => undefined);
      throw new FetchError(error ?? "Failed to fetch the public key", {
        response: res,
      });
    }
    const data = await res.json();
    const x509 = data[keyId];
    if (!x509) {
      throw new Error(`Public key "${keyId}" not found.`);
    }
    const key = await importX509(x509, "RS256");
    // Resolve the expiration time of the key
    const maxAge = res.headers.get("cache-control")?.match(/max-age=(\d+)/)?.[1]; // prettier-ignore
    const expires = Date.now() + Number(maxAge ?? "3600") * 1000;
    // Update the local cache
    cache.set(cacheKey, { key, expires });
    inFlight.delete(keyId);
    return key;
  }
  // Attempt to read the key from the local cache
  if (value) {
    if (value.expires > now + 10_000) {
      // If the key is about to expire, start a new request in the background
      if (value.expires - now < 600_000) {
        const promise = fetchKey();
        inFlight.set(cacheKey, promise);
        if (options.waitUntil) {
          options.waitUntil(promise);
        }
      }
      return value.key;
    }
    else {
      cache.delete(cacheKey);
    }
  }
  // Check if there is an in-flight request for the same key ID
  let promise = inFlight.get(cacheKey);
  // If not, start a new request
  if (!promise) {
    promise = fetchKey();
    inFlight.set(cacheKey, promise);
  }
  return await promise;
}

// based on https://www.npmjs.com/package/web-auth-library?activeTab=code
// made to check per Google's recommendations: https://developers.google.com/identity/gsi/web/guides/verify-google-id-token
export async function verifyIdToken(options) {
  if (!options?.idToken) {
    throw new TypeError(`Missing "idToken"`);
  }
  let clientId = options?.clientId;
  if (clientId === undefined) {
    throw new TypeError(`Missing "clientId"`);
  }
  if (!options.waitUntil && canUseDefaultCache) {
    console.warn("Missing `waitUntil` option.")
  }
  // Import the public key from the Google Cloud project
  const header = decodeProtectedHeader(options.idToken);
  const now = Math.floor(Date.now() / 1000);
  const key = await importPublicKey({
    keyId: header.kid,
    certificateURL: "https://www.googleapis.com/oauth2/v1/certs",
    waitUntil: options.waitUntil,
  });
  const { payload } = await jwtVerify(options.idToken, key, {
    audience: clientId,
    issuer: ['https://accounts.google.com','accounts.google.com'],
    maxTokenAge: "1h",
    clockTolerance: '5m'
  });
  if (!payload.sub) {
    throw new Error(`Missing "sub" claim`);
  }
  if (typeof payload.auth_time === "number" && payload.auth_time > now) {
    throw new Error(`Unexpected "auth_time" claim value`);
  }
  return payload;
}

Used like so:

let decoded = await verifyIdToken({
	idToken: googleLoginToken,
	clientId: env.GOOGLE_CLIENT_ID,
	waitUntil: context.waitUntil
})

Can't seem to import library

Hi,

Im trying to import the library using

import { getAccessToken } from "web-auth-library/google"; but I'm getting any error

./pages/api/tts_edge.ts:2:32
Type error: Cannot find module 'web-auth-library/google' or its corresponding type declarations.

  1 | import { NextRequest } from "next/server";
> 2 | import { getAccessToken } from "web-auth-library/google";
    |                                ^
  3 |
  4 | export const config = {
  5 | 	runtime: "experimental-edge",
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

What am I doing wrong?

No apiKey in Service Account Credentials

Google generated me JSON with following keys: type, project_id, private_key_id, private_key, client_email, client_id, auth_uri, token_uri, auth_provider_x509_cert_url, client_x509_cert_url, universe_domain. How should I create then a custom ID token using Service Account credentials? Your function throws error.

Inconsistencies

It looks like getIdToken uses a credentials object in the options, while verifyIdToken doesn't.

Getting idToken having an accessToken

Hey, thank you for this library, it is very useful for Cloudflare Workers.

Question, is there a way to get the idToken having user's accessToken?

What I'm trying to accomplish, is to validate the authenticated user's request with Firebase to get user's information and authorize an action.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.