diff --git a/apps/webapp/app/models/user.server.ts b/apps/webapp/app/models/user.server.ts
index 19d5a2ee75..13054b8bf1 100644
--- a/apps/webapp/app/models/user.server.ts
+++ b/apps/webapp/app/models/user.server.ts
@@ -66,6 +66,10 @@ export async function findOrCreateGithubUser({
   authenticationProfile,
   authenticationExtraParams,
 }: FindOrCreateGithub): Promise<LoggedInUser> {
+  if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(email)) {
+    throw new Error("This email is unauthorized");
+  }
+
   const name = authenticationProfile._json.name;
   let avatarUrl: string | undefined = undefined;
   if (authenticationProfile.photos[0]) {
diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx
index 84297410ff..54f546b23b 100644
--- a/apps/webapp/app/routes/login._index/route.tsx
+++ b/apps/webapp/app/routes/login._index/route.tsx
@@ -11,6 +11,7 @@ import {
 import { LoginPageLayout } from "~/components/LoginPageLayout";
 import { Button, LinkButton } from "~/components/primitives/Buttons";
 import { Fieldset } from "~/components/primitives/Fieldset";
+import { FormError } from "~/components/primitives/FormError";
 import { Header1 } from "~/components/primitives/Headers";
 import { NamedIcon } from "~/components/primitives/NamedIcon";
 import { Paragraph } from "~/components/primitives/Paragraph";
@@ -19,6 +20,10 @@ import type { LoaderType as RootLoader } from "~/root";
 import { isGithubAuthSupported } from "~/services/auth.server";
 import { commitSession, setRedirectTo } from "~/services/redirectTo.server";
 import { getUserId } from "~/services/session.server";
+import {
+  getUserSession,
+  commitSession as commitAuthSession,
+} from "~/services/sessionStorage.server";
 import { appEnvTitleTag } from "~/utils";
 import { requestUrl } from "~/utils/requestUrl.server";
 
@@ -41,11 +46,23 @@ export async function loader({ request }: LoaderFunctionArgs) {
   const url = requestUrl(request);
   const redirectTo = url.searchParams.get("redirectTo");
 
+  const session = await getUserSession(request);
+  const error = session.get("auth:error");
+
+  let githubError: string | undefined;
+  if (error) {
+    if ("message" in error) {
+      githubError = error.message;
+    } else {
+      githubError = JSON.stringify(error, null, 2);
+    }
+  }
+
   if (redirectTo) {
     const session = await setRedirectTo(request, redirectTo);
 
     return typedjson(
-      { redirectTo, showGithubAuth: isGithubAuthSupported },
+      { redirectTo, showGithubAuth: isGithubAuthSupported, githubError },
       {
         headers: {
           "Set-Cookie": await commitSession(session),
@@ -53,10 +70,12 @@ export async function loader({ request }: LoaderFunctionArgs) {
       }
     );
   } else {
-    return typedjson({
-      redirectTo: null,
-      showGithubAuth: isGithubAuthSupported,
-    });
+    return typedjson(
+      { redirectTo: null, showGithubAuth: isGithubAuthSupported, githubError },
+      {
+        headers: { "Set-Cookie": await commitAuthSession(session) },
+      }
+    );
   }
 }
 
@@ -78,7 +97,7 @@ export default function LoginPage() {
             Create an account or login
           </Paragraph>
           <Fieldset className="w-full">
-            <div className="flex flex-col gap-y-2">
+            <div className="flex flex-col items-center gap-y-2">
               {data.showGithubAuth && (
                 <Button
                   type="submit"
@@ -102,6 +121,8 @@ export default function LoginPage() {
                 />
                 Continue with Email
               </LinkButton>
+
+              {data.githubError && <FormError>{data.githubError}</FormError>}
             </div>
             <Paragraph variant="extra-small" className="mt-2 text-center">
               By signing up you agree to our{" "}
diff --git a/apps/webapp/app/routes/login.magic/route.tsx b/apps/webapp/app/routes/login.magic/route.tsx
index c19b684658..1af4a83107 100644
--- a/apps/webapp/app/routes/login.magic/route.tsx
+++ b/apps/webapp/app/routes/login.magic/route.tsx
@@ -1,7 +1,7 @@
 import { InboxArrowDownIcon } from "@heroicons/react/24/solid";
 import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
 import { redirect } from "@remix-run/node";
-import { Form, useNavigation } from "@remix-run/react";
+import { Form, useActionData, useNavigation } from "@remix-run/react";
 import { getMatchesData, metaV1 } from "@remix-run/v1-meta";
 import {
   TypedMetaFunction,
@@ -74,6 +74,18 @@ export async function action({ request }: ActionFunctionArgs) {
     .parse(payload);
 
   if (action === "send") {
+    const { email } = z
+      .object({
+        email: z.string().email(),
+      })
+      .parse(payload);
+
+    if (process.env.WHITELISTED_EMAILS && !new RegExp(process.env.WHITELISTED_EMAILS).test(email)) {
+      return {
+        error: "This email is unauthorized",
+      };
+    }
+
     return authenticator.authenticate("email-link", request, {
       successRedirect: "/login/magic",
       failureRedirect: "/login/magic",
@@ -92,6 +104,7 @@ export async function action({ request }: ActionFunctionArgs) {
 
 export default function LoginMagicLinkPage() {
   const { magicLinkSent, magicLinkError } = useTypedLoaderData<typeof loader>();
+  const actionData = useActionData<typeof action>();
   const navigate = useNavigation();
 
   const isLoading =
@@ -176,6 +189,7 @@ export default function LoginMagicLinkPage() {
                   />
                   {isLoading ? "Sending…" : "Send a magic link"}
                 </Button>
+                {actionData?.error && <FormError>{actionData?.error}</FormError>}
                 {magicLinkError && <FormError>{magicLinkError}</FormError>}
               </Fieldset>
               <Paragraph variant="extra-small" className="mb-4 mt-6 text-center">