JSPM

@boxyhq/remix-auth-sso

1.1.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q27088F
  • License Apache-2.0

An SSO strategy for Remix Auth, based on the OAuth2Strategy

Package Exports

  • @boxyhq/remix-auth-sso
  • @boxyhq/remix-auth-sso/build/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@boxyhq/remix-auth-sso) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

BoxyHQSSOStrategy

Attention ⚠️: We have deprecated the earlier strategy BoxyHQSAMLStrategy (npm package: @boxyhq/remix-auth-saml). In case you are using that one please consider changing over to this strategy.

The BoxyHQSSOStrategy can be used to enable Single Sign-On (SSO) in your remix app. It extends the OAuth2Strategy.

Demo

Checkout the demo at https://github.com/boxyhq/jackson-remix-auth

Supported runtimes

Runtime Has Support
Node.js
Cloudflare

SAML Jackson Service

SAML Jackson implements SSO as an OAuth 2.0 flow, abstracting away all the complexities of the underlying SAML/OIDC protocol.

You can deploy SAML Jackson as a separate service. Check out the documentation for more details

Configuration

SSO login requires a connection for every tenant/product of yours. One common method is to use the domain for an email address to figure out which tenant they belong to. You can also use a unique tenant ID (string) from your backend for this, typically some kind of account or organization ID.

Check out the documentation for more details.

Usage

Install the strategy

npm install @boxyhq/remix-auth-sso

Create the strategy instance

// app/utils/auth.server.ts
import { Authenticator } from "remix-auth";
import {
  BoxyHQSSOStrategy,
  type BoxyHQSSOProfile,
} from "@boxyhq/remix-auth-sso";

// Create an instance of the authenticator, pass a generic with what your
// strategies will return and will be stored in the session
export const authenticator = new Authenticator<BoxyHQSSOProfile>(
  sessionStorage
);

auth.use(
  new BoxyHQSSOStrategy(
    {
      issuer: "http://localhost:5225", // point this to the hosted jackson service
      clientID: "dummy", // The dummy here is necessary if the tenant and product are set dynamically from the client side
      clientSecret: "dummy", // The dummy here is necessary if the tenant and product are set dynamically from the client side
      callbackURL: new URL(
        "/auth/sso/callback",
        process.env.BASE_URL
      ).toString(), // BASE_URL should point to the application URL
    },
    async ({ profile }) => {
      return profile;
    }
  )
);

Setup your routes

// app/routes/login.tsx
export default function Login() {
  return (
    <Form method="post" action="/auth/sso">
      {/* We will be using user email to identify the tenant*/}
      <label htmlFor="email">Email</label>
      <input
        id="email"
        type="email"
        name="email"
        placeholder="johndoe@example.com"
        required
      />
      {/* Product can also be set dynamically, set to `demo` here */}
      <input type="text" name="product" hidden defaultValue="demo" />
      <button type="submit">Sign In with SSO</button>
    </Form>
  );
}
// app/routes/auth/sso.tsx
import { ActionFunction, json } from "remix";
import { auth } from "~/auth.server";
import invariant from "tiny-invariant";

type PostError = {
  email?: boolean;
  product?: boolean;
};
export const action: ActionFunction = async ({ request }) => {
  const formData = await request.formData();

  const email = formData.get("email");
  const product = await formData.get("product");

  const errors: PostError = {};
  if (!email) errors.email = true;
  if (!product) errors.product = true;

  if (Object.keys(errors).length) {
    return json(errors);
  }

  invariant(typeof email === "string");
  // Get the tenant from the domain
  const tenant = email.split("@")[1];
  return await auth.authenticate("boxyhq-sso", request, {
    successRedirect: "/private",
    failureRedirect: "/",
    context: {
      clientID: `tenant=${tenant}&product=${product}`,
      clientSecret: "dummy",
    },
  });
};
// app/routes/auth/sso/callback.tsx
import type { LoaderFunction } from "remix";
import { auth } from "~/auth.server";

export const loader: LoaderFunction = async ({ request, params }) => {
  return auth.authenticate("boxyhq-sso", request, {
    successRedirect: "/private",
    failureRedirect: "/",
  });
};