├── README.md ├── components ├── globalApiUrl.js ├── layout.js └── redirectTo.js ├── package.json ├── pages ├── _app.js ├── dashboard.js ├── forgot-password.js ├── index.js └── login.js └── server.js /README.md: -------------------------------------------------------------------------------- 1 | # nextjs-auth-skeleton 2 | NextJS server and client side authentication skeleton template for simple JWT tokens 3 | 4 | This took me a little while to get right. Thanks to [Tim](https://github.com/timneutkens) for basically showing me how to do all of this. 5 | 6 | If anyone has any suggestions, would love to hear feedback. 7 | 8 | ---- 9 | 10 | To use this you need another server with the following endpoints: 11 | 1. One that accepts an email/password combo and spits out an **authtoken** 12 | 2. One that checks said **authtoken** and gives you a success or fail response 13 | -------------------------------------------------------------------------------- /components/globalApiUrl.js: -------------------------------------------------------------------------------- 1 | export default "http://127.0.0.1:3030" -------------------------------------------------------------------------------- /components/layout.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import React from 'react' 3 | 4 | export default class extends React.Component { 5 | 6 | constructor (props) { 7 | super(props) 8 | this.state = { authtoken: props.authtoken } 9 | } 10 | 11 | render() { 12 | 13 | return ( 14 |
15 | 16 | Title Here 17 | 18 | 19 | 20 | { 21 | /* This is where the content in your other pages get's inserted to*/ 22 | this.props.children 23 | } 24 | 25 |
26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /components/redirectTo.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router' 2 | 3 | export default function redirectTo(destination, { res, status } = {}) { 4 | if (res) { 5 | res.writeHead(status || 302, { Location: destination }) 6 | res.end() 7 | } else { 8 | if (destination[0] === '/' && destination[1] !== '/') { 9 | Router.push(destination) 10 | } else { 11 | window.location = destination 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NextJS-with-Auth-Skeleton", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "express": "^4.16.3", 14 | "isomorphic-unfetch": "^2.0.0", 15 | "next": "^6.1.1", 16 | "next-cookies": "^1.0.2", 17 | "react": "^16.4.1", 18 | "react-dom": "^16.4.1" 19 | }, 20 | "devDependencies": {} 21 | } 22 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import API_URL from '../components/globalApiUrl.js' 2 | import App, {Container} from 'next/app' 3 | import React from 'react' 4 | import fetch from 'isomorphic-unfetch' 5 | import redirectTo from '../components/redirectTo.js' 6 | import cookies from 'next-cookies' 7 | 8 | export default class extends App { 9 | 10 | static async getInitialProps ({ Component, router, ctx }) { 11 | let pageProps = {}; 12 | const c = cookies(ctx); 13 | 14 | if (Component.getInitialProps) { 15 | pageProps = await Component.getInitialProps(ctx) 16 | } 17 | 18 | //if the authtoken is not found 19 | if(typeof c.authtoken == 'undefined') { 20 | //don't do anything if we are on a page that doesn't require credentials 21 | if(ctx.pathname == "/login" || ctx.pathname == "/forgot-password") return {pageProps}; 22 | //if we are on any other page, redirect to the login page 23 | else redirectTo('/login', { res: ctx.res, status: 301 }) 24 | } 25 | //if we do have an auth token to check 26 | else { 27 | 28 | var response = await fetch(API_URL+'/auth', { 29 | method: 'POST', headers: { 'Content-Type': 'application/json' }, 30 | body: JSON.stringify({ token:c.authtoken} ) 31 | }) 32 | .then( r => r.json() ) 33 | .then( resp => { 34 | 35 | if(ctx.pathname == "/") { 36 | 37 | //if auth check was successful, send to dashboard 38 | if(resp.result == "success") redirectTo('/dashboard', { res: ctx.res, status: 301 }) 39 | else { 40 | 41 | //setting the cookie to expire way back when removes it 42 | document.cookie = "authtoken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; 43 | redirectTo('/login', { res: ctx.res, status: 301 }) 44 | 45 | } 46 | 47 | } 48 | 49 | else if(ctx.pathname == "/login") { 50 | 51 | //shouldn't show the login page is we are already logged in 52 | if(resp.result == "success") { redirectTo('/dashboard', { res: ctx.res, status: 301 }); } 53 | 54 | //if it wasn't successful, stay where we are 55 | else return {...pageProps, ...{query: ctx.query, authtoken: c.authtoken}}; 56 | 57 | } 58 | 59 | //any other page that requires a login 60 | else { 61 | 62 | //if auth check was successful, stay where we are 63 | if(resp.result == "success") return {...pageProps, ...{query: ctx.query, authtoken: c.authtoken}}; 64 | 65 | //if it wasn't successful, clear the authtoken since it must be expired or invalid and redirect to login 66 | else { 67 | document.cookie = "authtoken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; 68 | redirectTo('/login', { res: ctx.res, status: 301 }); 69 | } 70 | } 71 | 72 | }) 73 | .catch((err) => { console.log(err); return {pageProps}; }) 74 | } 75 | 76 | if(response !== null) { return {response}; } 77 | else return {pageProps}; 78 | 79 | } 80 | 81 | render () { 82 | const {Component, pageProps} = this.props 83 | 84 | return 85 | 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pages/dashboard.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/layout.js' 2 | 3 | export default () => ( 4 | 5 | 6 |
Dashboard
7 |
8 | ) -------------------------------------------------------------------------------- /pages/forgot-password.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/layout.js' 2 | 3 | export default () => ( 4 | 5 |
Forgot Password
6 |
7 | ) -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/layout.js' 2 | 3 | export default () => ( 4 | 5 |

You should never see this.

6 |
7 | ) -------------------------------------------------------------------------------- /pages/login.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/layout.js' 2 | import API_URL from '../components/globalApiUrl.js' 3 | import Link from 'next/link' 4 | import Router from 'next/router' 5 | 6 | export default class extends React.Component { 7 | 8 | constructor (props) { 9 | super(props) 10 | this.state = { email: '', password: '', loggingIn: false, errorMessage: '' } 11 | 12 | this.handleChange = this.handleChange.bind(this); 13 | this.handleSubmit = this.handleSubmit.bind(this); 14 | } 15 | 16 | handleChange(event) { 17 | const target = event.target, value = target.value, name = target.name; 18 | this.setState({ [name]: value }); 19 | } 20 | 21 | handleSubmit(event) { 22 | 23 | event.preventDefault(); 24 | 25 | if(!this.state.loggingIn) { 26 | 27 | this.setState({loggingIn: true, errorMessage: ''}); 28 | 29 | fetch(API_URL+'/auth', { 30 | method: 'POST', headers: { 'Content-Type': 'application/json' }, 31 | body: JSON.stringify({ email: this.state.email, password: this.state.password }) 32 | }) 33 | .then( r => r.json() ) 34 | .then( resp => { 35 | 36 | /* returns a JSON object {result: "success"} or {error:""} with 400 status */ 37 | 38 | if(!resp.result) { 39 | this.setState({errorMessage: 'Email or Password is invalid.'}) 40 | } 41 | else { 42 | document.cookie = 'authtoken='+resp.token; 43 | window.location = "/dashboard"; 44 | } 45 | 46 | this.setState({loggingIn: false }); 47 | }) 48 | } 49 | } 50 | 51 | render = () => ( 52 | 53 |
54 |

Login

55 | 56 |
57 | 58 | {this.state.errorMessage} 59 | 60 | 61 | 62 | Forgot password? 63 | 64 |
65 | 66 |
67 |
68 | ) 69 | 70 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const next = require('next') 3 | 4 | const dev = process.env.NODE_ENV !== 'production' 5 | const app = next({ dev }) 6 | const handle = app.getRequestHandler() 7 | 8 | app.prepare() 9 | .then(() => { 10 | const server = express() 11 | 12 | server.get('*', (req, res) => { 13 | return handle(req, res) 14 | }) 15 | 16 | server.listen(3000, (err) => { 17 | if (err) throw err 18 | console.log('> Ready on http://localhost:3000') 19 | }) 20 | }) 21 | .catch((ex) => { 22 | console.error(ex.stack) 23 | process.exit(1) 24 | }) --------------------------------------------------------------------------------