├── 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 |
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 | })
--------------------------------------------------------------------------------