├── .eslintrc.json ├── .gitignore ├── lib ├── database.js └── passport.js ├── models └── User.js ├── next.config.js ├── package-lock.json ├── pages ├── _app.js ├── api │ ├── google │ │ ├── callback.js │ │ └── index.js │ └── hello.js ├── dashboard.js └── index.js ├── public ├── favicon.ico └── vercel.svg └── styles ├── Home.module.css └── globals.css /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /lib/database.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connect = () => { 4 | mongoose.connect( 5 | process.env.MONGODB_URI, 6 | { useNewUrlParser: true, useUnifiedTopology: true }, 7 | (err) => { 8 | if (err) console.log("Error connecting to MongoDB"); 9 | else console.log("Connected to MongoDB"); 10 | } 11 | ); 12 | }; 13 | 14 | export default connect; 15 | -------------------------------------------------------------------------------- /lib/passport.js: -------------------------------------------------------------------------------- 1 | import passport from "passport"; 2 | import { Strategy as GoogleStrategy } from "passport-google-oauth2"; 3 | import User from "../models/User"; 4 | import jwt from "jsonwebtoken"; 5 | 6 | passport.use( 7 | "google", 8 | new GoogleStrategy( 9 | { 10 | clientID: process.env.GOOGLE_CLIENT_ID, 11 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 12 | callbackURL: "http://localhost:3000/api/google/callback", 13 | }, 14 | async (accessToken, refreshToken, profile, done) => { 15 | try { 16 | const obj = await User.findOne({ email: profile.email }); 17 | if (!obj) { 18 | // create new user 19 | const newUser = new User({ 20 | email: profile.email, 21 | name: profile.displayName, 22 | accessToken, 23 | }); 24 | await newUser.save(); 25 | const token = await jwt.sign( 26 | { 27 | id: newUser._id, 28 | created: Date.now().toString(), 29 | }, 30 | process.env.JWT_SECRET 31 | ); 32 | newUser.tokens.push(token); 33 | await newUser.save(); 34 | done(null, newUser, { message: "Auth successful", token }); 35 | } else { 36 | // login existing user 37 | const token = await jwt.sign( 38 | { 39 | id: obj._id, 40 | created: Date.now().toString(), 41 | }, 42 | process.env.JWT_SECRET 43 | ); 44 | obj.tokens.push(token); 45 | await obj.save(); 46 | done(null, obj, { message: "Auth successful", token }); 47 | } 48 | } catch (err) { 49 | console.error(err); 50 | done(err, false, { message: "Internal server error" }); 51 | } 52 | } 53 | ) 54 | ); 55 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const UserSchema = mongoose.Schema({ 4 | name: String, 5 | email: String, 6 | accessToken: String, 7 | tokens: [String], 8 | }); 9 | 10 | let User; 11 | 12 | try { 13 | User = mongoose.model("users"); 14 | } catch (err) { 15 | User = mongoose.model("users", UserSchema); 16 | } 17 | 18 | export default User; 19 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return 5 | } 6 | 7 | export default MyApp 8 | -------------------------------------------------------------------------------- /pages/api/google/callback.js: -------------------------------------------------------------------------------- 1 | import { setCookies } from "cookies-next"; 2 | import passport from "passport"; 3 | import connect from "../../../lib/database"; 4 | import "../../../lib/passport"; 5 | 6 | export default async function (req, res, next) { 7 | await connect(); 8 | passport.authenticate("google", (err, user, info) => { 9 | if (err || !user) { 10 | return res.redirect("http://localhost:3000/?a=auth_fail"); 11 | } 12 | 13 | // set cookie and send redirect 14 | setCookies("token", info.token, { 15 | req, 16 | res, 17 | }); 18 | res.redirect("http://localhost:3000/dashboard"); 19 | })(req, res, next); 20 | } 21 | -------------------------------------------------------------------------------- /pages/api/google/index.js: -------------------------------------------------------------------------------- 1 | import passport from "passport"; 2 | import connect from "../../../lib/database"; 3 | import "../../../lib/passport"; 4 | 5 | export default async function (req, res, next) { 6 | await connect(); 7 | passport.authenticate("google", { 8 | scope: ["profile", "email"], 9 | session: false, 10 | })(req, res, next); 11 | } 12 | -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default function handler(req, res) { 4 | res.status(200).json({ name: 'John Doe' }) 5 | } 6 | -------------------------------------------------------------------------------- /pages/dashboard.js: -------------------------------------------------------------------------------- 1 | import { getCookie, removeCookies } from "cookies-next"; 2 | import Head from "next/head"; 3 | import React from "react"; 4 | import connect from "../lib/database"; 5 | import jwt from "jsonwebtoken"; 6 | import User from "../models/User"; 7 | import { useRouter } from "next/router"; 8 | 9 | function Dashboard({ name, email }) { 10 | const router = useRouter(); 11 | 12 | const logout = () => { 13 | removeCookies("token"); 14 | router.replace("/"); 15 | }; 16 | 17 | return ( 18 |
19 | 20 | Dashboard 21 | 22 |
Welcome {name}!
23 |
{email}
24 | 25 |
26 | ); 27 | } 28 | 29 | export async function getServerSideProps({ req, res }) { 30 | try { 31 | // connect db 32 | await connect(); 33 | // check cookie 34 | const token = getCookie("token", { req, res }); 35 | if (!token) 36 | return { 37 | redirect: { 38 | destination: "/", 39 | }, 40 | }; 41 | 42 | const verified = await jwt.verify(token, process.env.JWT_SECRET); 43 | const obj = await User.findOne({ _id: verified.id }); 44 | if (!obj) 45 | return { 46 | redirect: { 47 | destination: "/", 48 | }, 49 | }; 50 | return { 51 | props: { 52 | email: obj.email, 53 | name: obj.name, 54 | }, 55 | }; 56 | } catch (err) { 57 | removeCookies("token", { req, res }); 58 | return { 59 | redirect: { 60 | destination: "/", 61 | }, 62 | }; 63 | } 64 | } 65 | 66 | export default Dashboard; 67 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { checkCookies, getCookie, getCookies } from "cookies-next"; 2 | import Head from "next/head"; 3 | import Image from "next/image"; 4 | import styles from "../styles/Home.module.css"; 5 | 6 | export default function Home() { 7 | return ( 8 |
9 | 10 | Create Next App 11 | 12 | 13 | 14 | 15 | Login with Google 16 |
17 | ); 18 | } 19 | 20 | export async function getServerSideProps({ req, res }) { 21 | try { 22 | const cookieExists = getCookie("token", { req, res }); 23 | console.log(cookieExists); 24 | if (cookieExists) return { redirect: { destination: "/dashboard" } }; 25 | return { props: {} }; 26 | } catch (err) { 27 | return { props: {} }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaiward-dev/passport-auth-nextjs/edef3492a0a59f965489e0da040da6453db8c43b/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaiward-dev/passport-auth-nextjs/edef3492a0a59f965489e0da040da6453db8c43b/styles/Home.module.css -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | margin: 0; 17 | padding: 0; 18 | } 19 | --------------------------------------------------------------------------------