1.0.0 • Published 2 years ago

@boxyhq/remix-auth-saml v1.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

BoxyHQSAMLStrategy

The BoxyHQ SAML strategy is used to authenticate customers (typically enterprises having a SAML IdP) of your SaaS application. It extends the OAuth2Strategy.

Supported runtimes

RuntimeHas Support
Node.js
Cloudflare

BoxyHQ SAML Service

BoxyHQ SAML is an open source service that handles the SAML login flow as an OAuth 2.0 flow, abstracting away all the complexities of the SAML protocol.

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

Configuration

SAML login requires a configuration for every tenant 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-saml

Create the strategy instance

// app/utils/auth.server.ts
import { Authenticator } from "remix-auth";
import {
  BoxyHQSAMLStrategy,
  type BoxyHQSAMLProfile,
} from "@boxyhq/remix-auth-saml";

// 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<BoxyHQSAMLProfile>(sessionStorage);


auth.use(
  new BoxyHQSAMLStrategy(
    {
      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/saml/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/saml"
    >
      {/* 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/saml.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");

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

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