Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Auth Provider - NOOP in the streets, WorkOS in the sheets #2139

Open
3 tasks done
chronark opened this issue Sep 27, 2024 · 2 comments
Open
3 tasks done

[WIP] Auth Provider - NOOP in the streets, WorkOS in the sheets #2139

chronark opened this issue Sep 27, 2024 · 2 comments
Assignees
Labels
Core Team Feature New feature or request

Comments

@chronark
Copy link
Collaborator

chronark commented Sep 27, 2024

Preliminary Checks

Is your feature request related to a problem? Please describe.

Selfhosting Unkey requires setting up clerk.com right now, which is added friction for contributors and not ideal for enterprises.

At the same time, we - Unkey - do not want to roll browser-auth ourselves and want to keep using a service for it, who will develop their product further without any additional time investment from our side.

We came to a solution to drop auth by default when selfhosting and only using a provider for our managed service.
This offers the best of both worlds.

When you are running unkey locally, there will be no auth. You will simply be dropped into a workspace and can use the dashboard as usual and can even create more workspaces, but there is no sign in page or anything.

Enterprises usually already have a mechanism to protect internal dashboards and can simply run Unkey behind their auth stack.

Describe the solution

We need 3 things:

  1. an interface that abstracts the auth provider
  2. a local implementation of said interface that just returns hard coded ids or looks in our db
  3. a WorkOS implementation of the interface

The beauty of WorkOS is that it's 100% serverside, so creating an interface isn't that hard.
We can probably get started with something similar to this:

export interface Auth {
  // If there is none, it must trigger a redirect to the sign in page.
  getOrgId(): Promise<string>;

  // called in trpc, it returns just enough to know who's talking to us
  getSession(): Promise<{ userId: string, orgId: string} | null>;

  // called in RSC, giving us some display data for the user
  getUser(): Promise<{ userId: string, profileUrl: string, name: string }| null>;

  listOrganisations(): Promise<Organisation[]>

  // sign the user into a different workspace/organisation
  signIn(orgId?: string): Promise<void>

  signOut(): Promise<void>

  // update name, domain or picture
  updateOrg(org: Partial<Organisation>): Promise<void>
}

We will then have a WorkOS implementation for this interface, using a combination of authkit and the workos node sdk.

Specifying whether to use the Local or WorkOS implentation will be done with environment variables

// /apps/dashboard/lib/env.ts

AUTH_PROVIDER: z.enum(["workos", "local"]),

WORKOS_API_KEY: z.string().optional(),
WORKOS_CLIENT_ID: z.string().optional(),
NEXT_PUBLIC_WORKOS_REDIRECT_URI: z.string().default("http://localhost:3000/callback"),
WORKOS_COOKIE_PASSWORD: z.string().optional(),

and one implentation is chosen at runtime like so:

// /apps/dashboard/lib/auth/index.ts

import { env } from "@/lib/env";

import type { Auth } from "./interface";
import { LocalAuth } from "./local";
import { WorkosAuth } from "./workos";

export let auth: Auth;

let initialized = false;

function init() {
  if (initialized) {
    return;
  }

  switch (env().AUTH_PROVIDER) {
    case "workos":
      serverAuth = new WorkosAuth();
      break;
    case "local":
      serverAuth = new LocalAuth();
      break;
  }

  initialized = true;
}

init();

Throughout the app, we can then simply import auth and not care which implementation it is.

// anwhere/else.ts
import { auth } from "@/lib/auth"

const user = await auth.getUser()

Describe alternatives you have considered (if any)

No response

Additional context

No response

@chronark chronark added Feature New feature or request Needs Approval Needs approval from Unkey labels Sep 27, 2024
Copy link

linear bot commented Sep 27, 2024

@perkinsjr perkinsjr added Core Team and removed Needs Approval Needs approval from Unkey labels Oct 1, 2024
@chronark
Copy link
Collaborator Author

chronark commented Oct 3, 2024

Going to continue in a comment, cause it's been a while and you already started work.


Goals and expectations

  • We will do a full rip and replace, because we don't want to use both systems side by side. Before merging, there must be no clerk code left.
  • We want to reuse our existing auth pages, not authkits hosted pages.
  • We should do regular main pulls, especially during hacktober fest, there will be a lot of small contributions and it's probably much easier to address them one at a time, rather than getting caught in a large conflict at the end.
  • Don't follow the workos API too closely, create an abstraction that makes sense and is likely to work with different providers. If we use the workos API exactly, we didn't gain much from hiding it behind an interface at all.
  • Near the end, @perkinsjr can help with the clerk export
  • We can announce a downtime window for new signups and membership changes if we have to, while we migrate all users.

Things that are easy to forget:

  • Update the pnpm local script to remove clerk and insert the AUTH_PROVIDER env variable
  • Inbound webhooks are different now, but if you remove all of clerk's sdk, typescript should make this hard to forget. (Maybe we don't even need webhooks here and can actually trigger the email from within our signup flow)
  • Ping james or me to insert staging and production env vars

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Core Team Feature New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants