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

@assaf/react-one-tap

v3.0.0

Published

React component for Google One-Tap

Downloads

24

Readme

React component for one-tap sign-in with your Google account

Why?

One-Tap is a super simple UI for signing with your Google account.

In supported browsers, one-tap is a slick user experience. It pops an overlay at the top/right of the page, with your active Google account. It takes a single click to sign in.

(And it supports having multiple Google accounts and let you switch between them)

When you return to the app after your token has expired (~1 hour), it will automatically sign you in again with a new token. There's a short delay which allows you to cancel.

You're not going to be bouncing around through multiple pages, the jerky OAuth flow you get with "Sign in with X" buttons.

So this is the quickest and easiest way I found to add single sign-on.

No Free Launches

There are tradeoffs of course.

The user experience is great in supported browsers. That means Chrome, Firefox, and Edge (not Safari). On every operating system except for iOS and iPadOS, because those only have one browser (all other browsers are re-skinned Safari).

The user experience on unsupported browsers is not bad, it's just no better than OAuth.

This only works for users that have a Google account. However, you don't need a GMail or G Suite account. And many people have Google accounts, they use them for YouTube, Google Docs, Android, etc.

This UI doesn't blend well with other methods of authentication. So if you want to give users multiple options — email/password, magic link, Facebook, GitHub, etc — you should be using OAuth instead.

It should go without saying you're helping Google track users across the web. Because cookies are going away, the next best thing is to try and get everyone to sign in with their Google account.

Regardless …

I'm using this for "internal" apps. I know every user has a Google account, so not going to bother with other methods of authentication.

And we're using other Google products, so already signed into the account, so this is the easiest SSO experience.

If you trust Google to authenticate users, then it's a pretty good authentication mechanism. On the back-end verify users by checking email address, or email domain.

Install

yarn add @assaf/react-one-tap
node install @assaf/react-one-tap

Sign in/Sign out

import { GoogleOneTap } from "@assaf/react-one-tap";

function LoginRequired({ children }) {
  const buttonId = "google-sign-in-one-tap";

  return (
    <GoogleOneTap
      autoSelect={true}
      clientId={process.env.GOOGLE_CLIENT_ID}
      context="use"
      fallback={{ buttonId }}
      >
      {({ isSignedIn, profile, signOut }) => isSignedIn ? (
        <main className="regular-page">
          <header>
            Welcome back {profile.name}
            <button onClick={signOut}>sign out</button>
          </header>
          {children}
        </main>
      ) : (
        <main className="sign-in-page">
          <div id={buttonId} />
        </main>
      )}
    </GoogleOneTap>
  );
}

If the user is authenticated, then isSignIn is true. You also get the JWT access token (token) and their Google profile (profile).

The profile will include their name, email address, picture, etc. See Profile.

To allow users to sign out, you need to render a button that will call signOut. It will sign them out of all open tabs, and revoke their access token. It will not sign them out of other browsers/devices.

On Safari and iOS, if the user has not signed in before, then the one-tap overlay doesn't show. If you want these users to sign in, you need to use the fallback option.

That option expects the ID of a container element. It will render a sign-in button into that element.

Pass JWT Token to Server

With SWR you can do somethig like this:

import { useGoogleOneTap } from "@assaf/react-one-tap";

function MyComponent() {
  const { headers, signOut } = useGoogleOneTap();

  const { data, error } = useSWR(
    token ? "/api" : null,
    async (path) => {
      const response = await fetch(path, { headers });
      if (response.ok) {
        return await response.json();
      } else {
        const { error } = await response.json();
        throw new Error(error);
      }
    });
  ...
}

Or using SWRConfig:

import { useGoogleOneTap } from "@assaf/react-one-tap";

function WithFetcher({ children }) {
  const { headers, signOut } = useGoogleOneTap();

  async function fetcher(url) {
    if (!token) return null;

    const response = await fetch(url, { headers });
    if (response.ok) {
      return await response.json();
    } else {
      const { error } = await response.json();
      throw new Error(error);
    }
  };

  return <SWRConfig value={{ fetcher }}>{children}</SWRConfig>;
}

useGoogleOneTap returns the JWT access token (token) and for convenience the authorization header to send to the server (headers).

Authenticate on The Server

Create an authorization handler, varies by the server-side framework you use:

// Express, Connect, etc
import { authenticate } from "@assaf/react-one-tap";

const clientId = process.env.GOOGLE_CLIENT_ID;
const users = ['[email protected]'];

function authorize(handler) {
  return async function (request, response) {
    const { status, profile, message } = await authenticate({ clientId, request });
    if (!profile) return response.status(status).send(message);

    const isAuthorized = profile.email_verified && users.includes(profile.email);
    if (isAuthorized) handler(request, response);
    else response.status(403).send("Access denied");
  }
};

router.get('/customers', authorize(getCustomers));
// Next.js middelware
import { authenticate } from "@assaf/react-one-tap";

const clientId = process.env.GOOGLE_CLIENT_ID;
const users = ['[email protected]'];

export default async function middleware(request) {
  const { status, profile, message } = await authenticate({ clientId, request });
  if (!profile) return new Response(message, { status });

  const isAuthorized = profile.email_verified && users.includes(profile.email);
  if (!isAuthorized) throw new Response("Access denied!", { status: 403 });

  return NextResponse.next();
}

The authenticate function looks for an Authorization header with a bearer token in it. It verifies the token, this will reject revoked tokens (if the user signed out).

It returns three properties:

  • status is one of 200, 401, or 403
  • profile is the user's profile, if authenticated succesfully
  • message is an error message to go along with 401/403 status code

From the profile, you have access to the user's name, email address, and whether that address was verified. See Profile.

You can check if the account is a G Suite account by looking at profile.hd, and authenticate based on the domain (ie email: "[email protected]", hd: "example.com" ).

If you're storing state, use profile.sub, which is the unique and immutable user identifier.

This allows users to change their email address. If you're using their email address from the profile, you may want to save is every so often. And also consider the email_verified field.