├── README.md ├── .eslintrc.json ├── next.config.js ├── database.db ├── public ├── favicon.ico └── vercel.svg ├── lib ├── initialiseDB.js └── auth.js ├── pages ├── _app.js ├── index.js ├── api │ └── auth │ │ └── [...nextauth].js ├── login.js └── signup.js ├── styles ├── globals.css ├── Home.module.css └── auth.module.css ├── package.json ├── .gitignore ├── migrations └── 001-init.sql └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # Next-Auth Credentials Demo 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /database.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekrresa/next-auth-credentials-demo/HEAD/database.db -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ekrresa/next-auth-credentials-demo/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /lib/initialiseDB.js: -------------------------------------------------------------------------------- 1 | import sqlite3 from "sqlite3"; 2 | import { open } from "sqlite"; 3 | 4 | export async function initialiseDB() { 5 | const db = await open({ 6 | driver: sqlite3.Database, 7 | filename: "database.db", 8 | }); 9 | 10 | // await db.migrate() 11 | 12 | return db; 13 | } 14 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { SessionProvider } from "next-auth/react"; 2 | import "../styles/globals.css"; 3 | 4 | export default function MyApp({ Component, pageProps }) { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /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 | } 17 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | align-items: center; 7 | padding: 0 1rem; 8 | } 9 | 10 | .title { 11 | font-size: 1.4rem; 12 | } 13 | 14 | .button { 15 | background: #2e73cd; 16 | color: #fff; 17 | padding: 0.4rem 1rem; 18 | border-radius: 4px; 19 | border: 1px solid; 20 | cursor: pointer; 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-auth-demo", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "next": "12.0.9", 12 | "next-auth": "^4.3.1", 13 | "react": "17.0.2", 14 | "react-dom": "17.0.2", 15 | "sqlite": "^4.0.23", 16 | "sqlite3": "^5.0.2" 17 | }, 18 | "devDependencies": { 19 | "eslint": "8.8.0", 20 | "eslint-config-next": "12.0.9" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /migrations/001-init.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Up 3 | -------------------------------------------------------------------------------- 4 | 5 | CREATE TABLE IF NOT EXISTS users ( 6 | id INTEGER PRIMARY KEY AUTOINCREMENT, 7 | name VARCHAR(255) NOT NULL, 8 | email VARCHAR(255) NOT NULL, 9 | password VARCHAR(255) NOT NULL 10 | ); 11 | 12 | -------------------------------------------------------------------------------- 13 | -- Down 14 | -------------------------------------------------------------------------------- 15 | 16 | DROP TABLE IF EXISTS users; -------------------------------------------------------------------------------- /styles/auth.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: center; 6 | align-items: center; 7 | padding: 0 1rem; 8 | } 9 | 10 | .form { 11 | display: flex; 12 | flex-direction: column; 13 | width: 16rem; 14 | } 15 | 16 | .label { 17 | font-size: 0.9rem; 18 | margin-bottom: 3px; 19 | display: inline-block; 20 | } 21 | 22 | .input { 23 | margin-bottom: 1rem; 24 | padding: 0.35rem 0.4rem; 25 | font-size: 0.85rem; 26 | border-radius: 4px; 27 | border: 1px solid #606060; 28 | } 29 | 30 | .button { 31 | background: #2e73cd; 32 | color: #fff; 33 | padding: 0.6rem 0.4rem; 34 | border-radius: 4px; 35 | border: 1px solid; 36 | cursor: pointer; 37 | } 38 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useRouter } from "next/router"; 3 | import { signOut, useSession } from "next-auth/react"; 4 | import styles from "../styles/Home.module.css"; 5 | 6 | export default function Home() { 7 | const { data, status } = useSession(); 8 | const router = useRouter(); 9 | 10 | if (status === "loading") { 11 | /** 12 | * Session is being fetched 13 | */ 14 | return
Loading...
; 15 | } 16 | 17 | if (status === "unauthenticated") { 18 | router.push("/login"); 19 | } 20 | 21 | return ( 22 |
23 |

Hello, {data?.user?.name}

24 | 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ochuko Ekrresa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/auth.js: -------------------------------------------------------------------------------- 1 | import { initialiseDB } from "./initialiseDB"; 2 | 3 | export async function login(credentials) { 4 | const db = await initialiseDB(); 5 | const result = await db.get( 6 | `SELECT name, email, password FROM users WHERE email = ?`, 7 | [credentials.email] 8 | ); 9 | 10 | if (!result) throw new Error("Invalid credentials"); 11 | 12 | if (result.password !== credentials.password) 13 | throw new Error("Invalid credentials"); 14 | 15 | return { name: result.name, email: result.email }; 16 | } 17 | 18 | export async function signup(credentials) { 19 | const db = await initialiseDB(); 20 | 21 | const existingUser = await db.get(`SELECT 1 FROM users WHERE email = ?`, [ 22 | credentials.email, 23 | ]); 24 | 25 | if (existingUser) throw new Error("Email is already in use"); 26 | 27 | const result = await db.run( 28 | `INSERT INTO users (name, email, password) VALUES (?, ?, ?)`, 29 | [credentials.name, credentials.email, credentials.password] 30 | ); 31 | 32 | if (!result.changes) throw new Error("An error occurred"); 33 | 34 | return await db.get(`SELECT name, email FROM users WHERE id = ?`, [ 35 | result.lastID, 36 | ]); 37 | } 38 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import CredentialsProvider from "next-auth/providers/credentials"; 3 | import * as Auth from "../../../lib/auth"; 4 | 5 | export default NextAuth({ 6 | providers: [ 7 | CredentialsProvider({ 8 | id: "login", 9 | name: "Login", 10 | type: "credentials", 11 | async authorize(credentials) { 12 | try { 13 | return await Auth.login(credentials); 14 | } catch (error) { 15 | throw new Error(error.message); 16 | } 17 | }, 18 | }), 19 | CredentialsProvider({ 20 | id: "signup", 21 | name: "Signup", 22 | type: "credentials", 23 | async authorize(credentials) { 24 | try { 25 | return await Auth.signup(credentials); 26 | } catch (error) { 27 | throw new Error(error.message); 28 | } 29 | }, 30 | }), 31 | ], 32 | callbacks: { 33 | async signIn({ user }) { 34 | // console.log({ user }); 35 | if (user) return true; 36 | 37 | return false; 38 | }, 39 | async session({ session }) { 40 | session.user.isLoggedIn = true; 41 | return session; 42 | }, 43 | async jwt({ token, user }) { 44 | return token; 45 | }, 46 | }, 47 | // use env variable in production 48 | secret: "looselipssinkships", 49 | }); 50 | -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useRouter } from "next/router"; 3 | import { signIn } from "next-auth/react"; 4 | import styles from "../styles/auth.module.css"; 5 | 6 | export default function Login() { 7 | const router = useRouter(); 8 | const formRef = React.useRef(null); 9 | 10 | const handleSubmit = async (e) => { 11 | e.preventDefault(); 12 | 13 | const formData = new FormData(formRef.current); 14 | if (!formData.get("email") || !formData.get("password")) return; 15 | 16 | const response = await signIn("login", { 17 | email: formData.get("email"), 18 | password: formData.get("password"), 19 | redirect: false, 20 | }); 21 | 22 | if (response.error) return; 23 | 24 | router.push("/"); 25 | }; 26 | 27 | return ( 28 |
29 |

Login

30 |
31 | 32 | 38 | 39 | 40 | 46 | 49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /pages/signup.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useRouter } from "next/router"; 3 | import { signIn } from "next-auth/react"; 4 | import styles from "../styles/auth.module.css"; 5 | 6 | export default function Signup() { 7 | const router = useRouter(); 8 | const formRef = React.useRef(null); 9 | const handleSubmit = async (e) => { 10 | e.preventDefault(); 11 | 12 | const formData = new FormData(formRef.current); 13 | if ( 14 | !formData.get("email") || 15 | !formData.get("password") || 16 | !formData.get("name") 17 | ) 18 | return; 19 | 20 | const response = await signIn("signup", { 21 | name: formData.get("name"), 22 | email: formData.get("email"), 23 | password: formData.get("password"), 24 | redirect: false, 25 | }); 26 | 27 | if (response.error) return; 28 | 29 | router.push("/"); 30 | }; 31 | 32 | return ( 33 |
34 |

Signup

35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 |
49 |
50 | ); 51 | } 52 | --------------------------------------------------------------------------------