From b6bd9877c59f831974da27b3616fb64b5e6a8038 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 08:53:28 -0500 Subject: [PATCH 1/9] feat(auth): add next-auth authentication --- src/app/page.tsx | 23 ++++++++++++++++++++++- src/env.js | 16 +++++++--------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 2801364..6561ae0 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,24 @@ +import { authOptions, getServerAuthSession } from "~/server/auth"; + export default async function Home() { - return

Cooper

; + const session = await getServerAuthSession(); + + if (!session) { + return ( +
+

+ You are not signed in! +

+

Click Here

+
+ ); + } + + return ( +
+

+ Welcome, {session.user.name}! +

+
+ ); } diff --git a/src/env.js b/src/env.js index 2e63d6a..e8dfd45 100644 --- a/src/env.js +++ b/src/env.js @@ -18,10 +18,9 @@ export const env = createEnv({ .enum(["development", "test", "production"]) .default("development"), NEXTAUTH_SECRET: - // process.env.NODE_ENV === "production" - // ? z.string() - // : - z.string().optional(), + process.env.NODE_ENV === "production" + ? z.string() + : z.string().optional(), NEXTAUTH_URL: z.preprocess( // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL // Since NextAuth.js automatically uses the VERCEL_URL if present. @@ -29,9 +28,8 @@ export const env = createEnv({ // VERCEL_URL doesn't include `https` so it cant be validated as a URL process.env.VERCEL ? z.string() : z.string().url(), ), - // Optional for now -- remember to change this if have decided an auth provider - GOOGLE_CLIENT_ID: z.string().optional(), - GOOGLE_CLIENT_SECRET: z.string().optional(), + GOOGLE_CLIENT_ID: z.string(), + GOOGLE_CLIENT_SECRET: z.string(), }, /** @@ -52,8 +50,8 @@ export const env = createEnv({ NODE_ENV: process.env.NODE_ENV, NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, NEXTAUTH_URL: process.env.NEXTAUTH_URL, - GOOGLE_CLIENT_ID: process.env.DISCORD_CLIENT_ID, - GOOGLE_CLIENT_SECRET: process.env.DISCORD_CLIENT_SECRET, + GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially From 957ea77f8b1af6f8e4c697b6025e5215094a5f66 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 09:08:56 -0500 Subject: [PATCH 2/9] docs(auth): update .env requirements in readme and example --- .env.example | 7 ++++++- README.md | 13 ++++++++++++- src/app/page.tsx | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index a1565d7..dc00890 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,7 @@ DATABASE_URL="postgresql://admin:admin@localhost:5432/cooper?schema=public" -NEXTAUTH_URL="localhost:3000" \ No newline at end of file + +NEXTAUTH_URL="http://localhost:3000" +NEXTAUTH_SECRET= + +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= \ No newline at end of file diff --git a/README.md b/README.md index ae21254..003d640 100644 --- a/README.md +++ b/README.md @@ -72,5 +72,16 @@ cp .env.example .env ```env DATABASE_URL="postgresql://admin:admin@localhost:5432/cooper?schema=public" -NEXTAUTH_URL="localhost:3000" + +NEXTAUTH_URL="http://localhost:3000" +NEXTAUTH_SECRET= + +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +``` + +To generate `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`, see [Setting up OAuth 2.0](https://support.google.com/cloud/answer/6158849?hl=en). To generate a new `NEXTAUTH_SECRET`, run the following command in your terminal and add it to the `.env` file. + +```bash +openssl rand -base64 32 ``` diff --git a/src/app/page.tsx b/src/app/page.tsx index 6561ae0..c5369f3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,4 +1,4 @@ -import { authOptions, getServerAuthSession } from "~/server/auth"; +import { getServerAuthSession } from "~/server/auth"; export default async function Home() { const session = await getServerAuthSession(); From f74f3adc55bdfbac3a7dfafa29ff74756bbfa3e8 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 09:30:59 -0500 Subject: [PATCH 3/9] feat(auth): create an example sign-in sign-out workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only a demo 🙏 --- src/app/page.tsx | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index c5369f3..1f46df6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,4 @@ +import { redirect } from "next/navigation"; import { getServerAuthSession } from "~/server/auth"; export default async function Home() { @@ -5,20 +6,46 @@ export default async function Home() { if (!session) { return ( -
+

You are not signed in!

-

Click Here

+ + + Sign in with Google +
); } return ( -
+

Welcome, {session.user.name}!

+ + Sign Out +
); } From 62ffead6e8f4535e72742867c2a1387c72054dfc Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 09:44:19 -0500 Subject: [PATCH 4/9] feat(auth): add vercel preview credential provider --- src/server/auth.ts | 65 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/src/server/auth.ts b/src/server/auth.ts index 79d92bf..5bd4035 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -9,6 +9,8 @@ import GoogleProvider from "next-auth/providers/google"; import { env } from "~/env"; import { db } from "~/server/db"; +import CredentialsProvider from "next-auth/providers/credentials"; + /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` * object and keep type safety. @@ -30,6 +32,53 @@ declare module "next-auth" { // } } +/** + * Provider to be used in a production environment. + */ +const productionProviders = [ + GoogleProvider({ + clientId: env.GOOGLE_CLIENT_ID ?? "", + clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", + }), + /** + * ...add more providers here. + * + * Most other providers require a bit more work than the Discord provider. For example, the + * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account + * model. Refer to the NextAuth.js docs for the provider you want to use. Example: + * + * @see https://next-auth.js.org/providers/github + */ +]; + +/** + * Provider to be used in a Vercel Preview + */ +const previewProvider = [ + CredentialsProvider({ + name: "TEST USER", + id: "cooper-test-provider", + async authorize() { + return { + id: "1", + name: "Cooper Test User", + email: "testuser@husky.neu.edu", + image: "https://i.pravatar.cc/150?u=jsmith@example.com", + }; + }, + credentials: {}, + }), +]; + +const getProvider = () => { + // VERCEL_ENV is not defined in src/env.js + if (process.env.VERCEL_ENV === "preview") { + return previewProvider; + } else { + return productionProviders; + } +}; + /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * @@ -46,21 +95,7 @@ export const authOptions: NextAuthOptions = { }), }, adapter: PrismaAdapter(db), - providers: [ - GoogleProvider({ - clientId: env.GOOGLE_CLIENT_ID ?? "", - clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", - }), - /** - * ...add more providers here. - * - * Most other providers require a bit more work than the Discord provider. For example, the - * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account - * model. Refer to the NextAuth.js docs for the provider you want to use. Example: - * - * @see https://next-auth.js.org/providers/github - */ - ], + providers: getProvider(), }; /** From 3918773bade9a80966cef46851fe57683c4d6e07 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 09:52:39 -0500 Subject: [PATCH 5/9] fix(deployment): add mock env variables to workflow --- .github/workflows/main.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c31eb4d..3e31a5b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,9 +10,12 @@ env: NODE_VERSION: 20 PNPM_VERSION: 8 - # Find a workaround to this. + # Find a workaround for this. DATABASE_URL: "postgresql://admin:admin@localhost:5432/cooper?schema=public" NEXTAUTH_URL: "localhost:3000" + NEXTAUTH_SECRET: "sec" + GOOGLE_CLIENT_ID: "cooper" + GOOGLE_CLIENT_SECRET: "cooper" jobs: lint: From d537d9d667f4c252b4c0bc185f6884a489452018 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 10:07:06 -0500 Subject: [PATCH 6/9] fix(auth): update next auth url in vercel previews --- src/app/page.tsx | 4 ++-- src/env.js | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 1f46df6..6b8abe8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -23,9 +23,9 @@ export default async function Home() { viewBox="0 0 18 19" > Sign in with Google diff --git a/src/env.js b/src/env.js index e8dfd45..a292134 100644 --- a/src/env.js +++ b/src/env.js @@ -21,13 +21,15 @@ export const env = createEnv({ process.env.NODE_ENV === "production" ? z.string() : z.string().optional(), - NEXTAUTH_URL: z.preprocess( - // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL - // Since NextAuth.js automatically uses the VERCEL_URL if present. - (str) => process.env.VERCEL_URL ?? str, - // VERCEL_URL doesn't include `https` so it cant be validated as a URL - process.env.VERCEL ? z.string() : z.string().url(), - ), + NEXTAUTH_URL: z + .preprocess( + // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL + // Since NextAuth.js automatically uses the VERCEL_URL if present. + (str) => process.env.VERCEL_URL ?? str, + // VERCEL_URL doesn't include `https` so it cant be validated as a URL + process.env.VERCEL ? z.string() : z.string().url(), + ) + .optional(), GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_SECRET: z.string(), }, From 54b2c23c5ea958a53bc2de17142a557a2ca92ab8 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 10:14:06 -0500 Subject: [PATCH 7/9] revert(auth): remove preview credential provider --- src/env.js | 16 +++++------- src/server/auth.ts | 65 +++++++++++----------------------------------- 2 files changed, 22 insertions(+), 59 deletions(-) diff --git a/src/env.js b/src/env.js index a292134..e8dfd45 100644 --- a/src/env.js +++ b/src/env.js @@ -21,15 +21,13 @@ export const env = createEnv({ process.env.NODE_ENV === "production" ? z.string() : z.string().optional(), - NEXTAUTH_URL: z - .preprocess( - // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL - // Since NextAuth.js automatically uses the VERCEL_URL if present. - (str) => process.env.VERCEL_URL ?? str, - // VERCEL_URL doesn't include `https` so it cant be validated as a URL - process.env.VERCEL ? z.string() : z.string().url(), - ) - .optional(), + NEXTAUTH_URL: z.preprocess( + // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL + // Since NextAuth.js automatically uses the VERCEL_URL if present. + (str) => process.env.VERCEL_URL ?? str, + // VERCEL_URL doesn't include `https` so it cant be validated as a URL + process.env.VERCEL ? z.string() : z.string().url(), + ), GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_SECRET: z.string(), }, diff --git a/src/server/auth.ts b/src/server/auth.ts index 5bd4035..79d92bf 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -9,8 +9,6 @@ import GoogleProvider from "next-auth/providers/google"; import { env } from "~/env"; import { db } from "~/server/db"; -import CredentialsProvider from "next-auth/providers/credentials"; - /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` * object and keep type safety. @@ -32,53 +30,6 @@ declare module "next-auth" { // } } -/** - * Provider to be used in a production environment. - */ -const productionProviders = [ - GoogleProvider({ - clientId: env.GOOGLE_CLIENT_ID ?? "", - clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", - }), - /** - * ...add more providers here. - * - * Most other providers require a bit more work than the Discord provider. For example, the - * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account - * model. Refer to the NextAuth.js docs for the provider you want to use. Example: - * - * @see https://next-auth.js.org/providers/github - */ -]; - -/** - * Provider to be used in a Vercel Preview - */ -const previewProvider = [ - CredentialsProvider({ - name: "TEST USER", - id: "cooper-test-provider", - async authorize() { - return { - id: "1", - name: "Cooper Test User", - email: "testuser@husky.neu.edu", - image: "https://i.pravatar.cc/150?u=jsmith@example.com", - }; - }, - credentials: {}, - }), -]; - -const getProvider = () => { - // VERCEL_ENV is not defined in src/env.js - if (process.env.VERCEL_ENV === "preview") { - return previewProvider; - } else { - return productionProviders; - } -}; - /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * @@ -95,7 +46,21 @@ export const authOptions: NextAuthOptions = { }), }, adapter: PrismaAdapter(db), - providers: getProvider(), + providers: [ + GoogleProvider({ + clientId: env.GOOGLE_CLIENT_ID ?? "", + clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", + }), + /** + * ...add more providers here. + * + * Most other providers require a bit more work than the Discord provider. For example, the + * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account + * model. Refer to the NextAuth.js docs for the provider you want to use. Example: + * + * @see https://next-auth.js.org/providers/github + */ + ], }; /** From 153b230c27bb4dd425abda9503a8dfdaae0caa78 Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 10:23:34 -0500 Subject: [PATCH 8/9] feat(auth): add credential provider again --- .github/workflows/main.yml | 1 - src/server/auth.ts | 63 +++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3e31a5b..f68ba71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,6 @@ env: # Find a workaround for this. DATABASE_URL: "postgresql://admin:admin@localhost:5432/cooper?schema=public" - NEXTAUTH_URL: "localhost:3000" NEXTAUTH_SECRET: "sec" GOOGLE_CLIENT_ID: "cooper" GOOGLE_CLIENT_SECRET: "cooper" diff --git a/src/server/auth.ts b/src/server/auth.ts index 79d92bf..ac7e6ad 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -5,6 +5,7 @@ import { type NextAuthOptions, } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; +import CredentialsProvider from "next-auth/providers/credentials"; import { env } from "~/env"; import { db } from "~/server/db"; @@ -30,6 +31,52 @@ declare module "next-auth" { // } } +/** + * Provider to be used in a production environment. + */ +const productionProviders = [ + GoogleProvider({ + clientId: env.GOOGLE_CLIENT_ID ?? "", + clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", + }), + /** + * ...add more providers here. + * + * Most other providers require a bit more work than the Discord provider. For example, the + * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account + * model. Refer to the NextAuth.js docs for the provider you want to use. Example: + * + * @see https://next-auth.js.org/providers/github + */ +]; + +/** + * Provider to be used in a Vercel Preview + */ +const previewProvider = [ + CredentialsProvider({ + name: "TEST USER", + id: "cooper-test-provider", + async authorize() { + return { + id: "1", + name: "Cooper Test User", + email: "testuser@husky.neu.edu", + image: "https://i.pravatar.cc/150?u=jsmith@example.com", + }; + }, + credentials: {}, + }), +]; + +const getProvider = () => { + if (process.env.VERCEL_ENV === "preview") { + return previewProvider; + } else { + return productionProviders; + } +}; + /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * @@ -46,21 +93,7 @@ export const authOptions: NextAuthOptions = { }), }, adapter: PrismaAdapter(db), - providers: [ - GoogleProvider({ - clientId: env.GOOGLE_CLIENT_ID ?? "", - clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", - }), - /** - * ...add more providers here. - * - * Most other providers require a bit more work than the Discord provider. For example, the - * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account - * model. Refer to the NextAuth.js docs for the provider you want to use. Example: - * - * @see https://next-auth.js.org/providers/github - */ - ], + providers: getProvider(), }; /** From 7ff9a8c2b656ce0c8468a4bf9646a0aecaf9a62d Mon Sep 17 00:00:00 2001 From: Rishikesh Kanabar Date: Sat, 3 Feb 2024 10:29:25 -0500 Subject: [PATCH 9/9] revert(auth): revert to old code --- .github/workflows/main.yml | 1 + src/server/auth.ts | 64 +++++++++----------------------------- 2 files changed, 16 insertions(+), 49 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f68ba71..3e31a5b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,6 +12,7 @@ env: # Find a workaround for this. DATABASE_URL: "postgresql://admin:admin@localhost:5432/cooper?schema=public" + NEXTAUTH_URL: "localhost:3000" NEXTAUTH_SECRET: "sec" GOOGLE_CLIENT_ID: "cooper" GOOGLE_CLIENT_SECRET: "cooper" diff --git a/src/server/auth.ts b/src/server/auth.ts index ac7e6ad..389a9e6 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -5,7 +5,6 @@ import { type NextAuthOptions, } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; -import CredentialsProvider from "next-auth/providers/credentials"; import { env } from "~/env"; import { db } from "~/server/db"; @@ -30,53 +29,6 @@ declare module "next-auth" { // // role: UserRole; // } } - -/** - * Provider to be used in a production environment. - */ -const productionProviders = [ - GoogleProvider({ - clientId: env.GOOGLE_CLIENT_ID ?? "", - clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", - }), - /** - * ...add more providers here. - * - * Most other providers require a bit more work than the Discord provider. For example, the - * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account - * model. Refer to the NextAuth.js docs for the provider you want to use. Example: - * - * @see https://next-auth.js.org/providers/github - */ -]; - -/** - * Provider to be used in a Vercel Preview - */ -const previewProvider = [ - CredentialsProvider({ - name: "TEST USER", - id: "cooper-test-provider", - async authorize() { - return { - id: "1", - name: "Cooper Test User", - email: "testuser@husky.neu.edu", - image: "https://i.pravatar.cc/150?u=jsmith@example.com", - }; - }, - credentials: {}, - }), -]; - -const getProvider = () => { - if (process.env.VERCEL_ENV === "preview") { - return previewProvider; - } else { - return productionProviders; - } -}; - /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * @@ -93,7 +45,21 @@ export const authOptions: NextAuthOptions = { }), }, adapter: PrismaAdapter(db), - providers: getProvider(), + providers: [ + GoogleProvider({ + clientId: env.GOOGLE_CLIENT_ID ?? "", + clientSecret: env.GOOGLE_CLIENT_SECRET ?? "", + }), + /** + * ...add more providers here. + * + * Most other providers require a bit more work than the Discord provider. For example, the + * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account + * model. Refer to the NextAuth.js docs for the provider you want to use. Example: + * + * @see https://next-auth.js.org/providers/github + */ + ], }; /**