Skip to content

Instantly share code, notes, and snippets.

@mattkinnersley
Last active August 23, 2024 06:44
Show Gist options
  • Save mattkinnersley/9aa751dc7ea115cdebbe2bf44a068ec7 to your computer and use it in GitHub Desktop.
Save mattkinnersley/9aa751dc7ea115cdebbe2bf44a068ec7 to your computer and use it in GitHub Desktop.
SST Ion Auth + Remix
export async function loader({ request, params }: LoaderFunctionArgs) {
if (params.path === "callback") {
const { searchParams } = new URL(request.url);
const code = searchParams.get("code");
if (code) {
const response = await fetch(import.meta.env.VITE_AUTH_URL + "/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: "remix",
code,
redirect_uri: `${import.meta.env.VITE_URL}/auth/callback`,
}),
});
if (response.ok) {
const { access_token } = await response.json();
return redirect("/org", {
headers: {
"Set-Cookie": `auth_token=${access_token}; Path=/; Max-Age=2592000`,
},
});
}
return redirect("/404");
}
} else if (params.path === "logout") {
return redirect("/", {
headers: {
"Set-Cookie": `auth_token=null; Path=/; Max-Age=0`,
},
});
}
return redirect("/404");
}
import { Resource } from "sst";
import { auth } from "sst/aws/auth";
import { GithubAdapter, GoogleAdapter } from "sst/auth/adapter";
import { Octokit } from "@octokit/core";
import { session } from "./session";
export const handler = auth.authorizer({
session,
providers: {
google: GoogleAdapter({
mode: "oidc",
clientID: Resource.GoogleClientID.value,
}),
github: GithubAdapter({
clientID: Resource.GithubClientID.value,
clientSecret: Resource.GithubClientSecret.value,
scope: "user",
mode: "oauth",
}),
},
callbacks: {
auth: {
async allowClient(clientID: string, redirect: string) {
return true;
},
async success(ctx, input, req) {
if (input.provider !== "github" && input.provider !== "google") {
throw new Error("Unknown provider");
}
let claims = null;
if (input.provider === "github") {
const octokit = new Octokit({
auth: input.tokenset.access_token,
});
const { data } = await octokit.request("GET /user/emails", {
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
const emailData = data?.find((email) => Boolean(email.primary));
claims = { email: emailData?.email };
}
if (input.provider === "google") {
claims = input.tokenset.claims();
}
if (!claims) {
throw Error("No claims found");
}
if (!claims.email) {
throw Error("No email found");
}
const email = claims.email;
const name = claims?.name ? claims.name : claims.email;
if (email && name) {
// Create user here
return ctx.session({
type: "user",
properties: { email: user.email },
});
}
throw new Error("Unknown provider");
},
},
},
});
export async function loader({ request }: LoaderFunctionArgs) {
const apiClient = createAPIClientFromRequest(request);
const data = await apiClient.getUser();
if (data?.user) {
return redirect("/org");
} else {
return null;
}
}
export default function Login() {
const AUTH_URL = new URL(import.meta.env.VITE_AUTH_URL).origin;
const THIS_URL = new URL(import.meta.env.VITE_URL).origin;
const params = new URLSearchParams({
client_id: "remix",
response_type: "code",
redirect_uri: `${THIS_URL}/auth/callback`,
});
return (
<PageContainer>
<Section className="flex-col w-5/6 md:w-2/3 mt-28 text-center">
<PageTitle>Login</PageTitle>
<div className="flex flex-col gap-4 justify-center w-full md:w-3/6">
<LinkButton
className="w-full"
to={AUTH_URL + `/google/authorize/?${params.toString()}`}
>
<Icons.google className="mr-2 h-4 w-4 inline" />
Continue with Google
</LinkButton>
<LinkButton
className="w-full"
to={AUTH_URL + `/github/authorize/?${params.toString()}`}
>
<Icons.gitHub className="mr-2 h-4 w-4 inline" />
Continue with GitHub
</LinkButton>
</div>
</Section>
</PageContainer>
);
}
import { createSessionBuilder } from "sst/auth";
export const session = createSessionBuilder<{
user: {
email: string;
};
apiKey: {
orgId: string;
spaceId: string;
environmentId: string;
keyId: string;
displayName: string;
};
}>();
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "app",
removal: input?.stage === "production" ? "retain" : "remove",
home: "aws",
};
},
async run() {
const secrets = {
GithubClientID: new sst.Secret("GithubClientID"),
GithubClientSecret: new sst.Secret("GithubClientSecret"),
GoogleClientID: new sst.Secret("GoogleClientID"),
GoogleClientSecret: new sst.Secret("GoogleClientSecret"),
};
const auth = new sst.aws.Auth("Auth", {
authenticator: {
link: [
db,
secrets.GithubClientID,
secrets.GithubClientSecret,
secrets.GoogleClientID,
secrets.GoogleClientSecret,
],
handler: "packages/functions/auth/index.handler",
},
});
new sst.aws.Remix("Frontend", {
path: "packages/web",
environment: {
VITE_AUTH_URL: auth.url,
VITE_URL:
$app.stage === "production"
? "https://myapp.dev"
: "http://localhost:5173",
},
});
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment