├── .gitignore ├── README.md ├── components ├── Layout.js ├── head.js └── nav.js ├── config.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── index.js ├── signin.js ├── signup.js └── users.js ├── redux ├── actions │ ├── authActions.js │ └── index.js ├── index.js ├── reducers │ ├── authReducer.js │ └── index.js └── types.js ├── static ├── favicon.ico ├── jwt_next_redux.jpg └── nextjs.jpg └── utils ├── cookie.js └── initialize.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | /.next 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JWT Authentication using Redux in Next.js 2 | 3 | ![Cover](static/jwt_next_redux.jpg?raw=true "Cover") 4 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import Head from 'next/head'; 3 | import { connect } from 'react-redux'; 4 | import actions from '../redux/actions'; 5 | 6 | const Layout = ({ children, title, isAuthenticated, deauthenticate }) => ( 7 |
8 | 9 | { title } 10 | 11 | 12 | 13 | 14 |
15 | 22 |
23 | 24 |
25 | { children } 26 |
27 |
28 | ); 29 | 30 | const mapStateToProps = (state) => ( 31 | {isAuthenticated: !!state.authentication.token} 32 | ); 33 | 34 | export default connect(mapStateToProps, actions)(Layout); -------------------------------------------------------------------------------- /components/head.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NextHead from 'next/head' 3 | import { string } from 'prop-types' 4 | 5 | const defaultDescription = '' 6 | const defaultOGURL = '' 7 | const defaultOGImage = '' 8 | 9 | const Head = props => ( 10 | 11 | 12 | {props.title || ''} 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ) 36 | 37 | Head.propTypes = { 38 | title: string, 39 | description: string, 40 | url: string, 41 | ogImage: string 42 | } 43 | 44 | export default Head 45 | -------------------------------------------------------------------------------- /components/nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | const links = [ 5 | { href: 'https://github.com/segmentio/create-next-app', label: 'Github' } 6 | ].map(link => { 7 | link.key = `nav-link-${link.href}-${link.label}` 8 | return link 9 | }) 10 | 11 | const Nav = () => ( 12 | 57 | ) 58 | 59 | export default Nav 60 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | API: 'http://localhost:8000/api/v1', 3 | }; -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | webpack: config => { 3 | // Fixes npm packages that depend on `fs` module 4 | config.node = { 5 | fs: 'empty' 6 | } 7 | 8 | return config 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-next-example-app", 3 | "scripts": { 4 | "dev": "next", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "axios": "^0.21.1", 10 | "cors": "^2.8.5", 11 | "js-cookie": "^2.2.0", 12 | "next": "9.3.2", 13 | "next-redux-wrapper": "^3.0.0-alpha.1", 14 | "react": "16.8.3", 15 | "react-dom": "16.8.3", 16 | "react-redux": "^6.0.1", 17 | "redux": "^4.0.1", 18 | "redux-thunk": "^2.3.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux'; 2 | import App, { Container } from 'next/app'; 3 | import withRedux from 'next-redux-wrapper'; 4 | import { initStore } from '../redux'; 5 | 6 | export default withRedux(initStore, { debug: true })( 7 | class MyApp extends App { 8 | static async getInitialProps({ Component, ctx }) { 9 | return { 10 | pageProps: { 11 | ...(Component.getInitialProps 12 | ? await Component.getInitialProps(ctx) 13 | : {}) 14 | } 15 | }; 16 | } 17 | 18 | render() { 19 | const { Component, pageProps, store } = this.props; 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | } 29 | ); -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import initialize from '../utils/initialize'; 3 | import Layout from '../components/Layout'; 4 | 5 | const Index = () => ( 6 | 7 |

Authentication with Next.js using JWT and Redux

8 | 9 |

10 | A demonstrating app of Next.js application using JWT with Redux. 11 |

12 |
13 | ); 14 | 15 | Index.getInitialProps = function(ctx) { 16 | initialize(ctx); 17 | }; 18 | 19 | export default connect(state => state)(Index); -------------------------------------------------------------------------------- /pages/signin.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import actions from '../redux/actions'; 4 | import initialize from '../utils/initialize'; 5 | import Layout from '../components/Layout'; 6 | 7 | class Signin extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | email: '', 12 | password: '' 13 | }; 14 | } 15 | 16 | static getInitialProps(ctx) { 17 | initialize(ctx); 18 | } 19 | 20 | handleSubmit(e) { 21 | e.preventDefault(); 22 | this.props.authenticate( 23 | { email_id: this.state.email_id, password: this.state.password }, 24 | 'login' 25 | ); 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 |

Sign In

32 |
37 |
38 |

39 | this.setState({ email_id: e.target.value })} 46 | /> 47 |

48 |
49 |
50 |

51 | this.setState({ password: e.target.value })} 58 | /> 59 |

60 |
61 |
62 |

63 | 66 |

67 |
68 |
69 |
70 | ); 71 | } 72 | } 73 | 74 | export default connect( 75 | state => state, 76 | actions 77 | )(Signin); -------------------------------------------------------------------------------- /pages/signup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Layout from '../components/Layout'; 4 | import actions from '../redux/actions'; 5 | import initialize from '../utils/initialize'; 6 | 7 | class Signup extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | firstname:'', 12 | lastname:'', 13 | email_id:'', 14 | mobile_no:'', 15 | password: '', 16 | confirm_password:'' 17 | }; 18 | } 19 | 20 | static getInitialProps(ctx) { 21 | initialize(ctx); 22 | } 23 | 24 | handleSubmit(e) { 25 | e.preventDefault(); 26 | console.log(this.state); 27 | this.props.register( 28 | { firstname: this.state.firstname, lastname: this.state.lastname, email_id: this.state.email_id, mobile_no:this.state.mobile_no, password: this.state.password, confirm_password:this.state.confirm_password }, 29 | 'register' 30 | ); 31 | } 32 | 33 | render() { 34 | return ( 35 | 36 |

Sign Up

37 |
42 |
43 |

44 | this.setState({ firstname: e.target.value })} 51 | /> 52 |

53 |
54 |
55 |

56 | this.setState({ lastname: e.target.value })} 63 | /> 64 |

65 |
66 |
67 |

68 | this.setState({ mobile_no: e.target.value })} 75 | /> 76 |

77 |
78 |
79 |

80 | this.setState({ email_id: e.target.value })} 87 | /> 88 |

89 |
90 |
91 |

92 | this.setState({ password: e.target.value })} 99 | /> 100 |

101 |
102 |
103 |

104 | this.setState({ confirm_password: e.target.value })} 111 | /> 112 |

113 |
114 |
115 |

116 | 119 |

120 |
121 |
122 |
123 | ); 124 | } 125 | } 126 | 127 | export default connect( 128 | state => state, 129 | actions 130 | )(Signup); -------------------------------------------------------------------------------- /pages/users.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import initialize from '../utils/initialize'; 4 | import Layout from '../components/Layout'; 5 | import actions from '../redux/actions'; 6 | 7 | class Users extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | }; 12 | } 13 | 14 | static async getInitialProps(ctx) { 15 | initialize(ctx); 16 | } 17 | 18 | async componentDidMount() { 19 | await this.props.getUser( 20 | { token: this.props.authentication.token }, 21 | 'profile' 22 | ); 23 | } 24 | render() { 25 | console.log(this.props.authentication.user); 26 | return this.props.authentication.user ? ( 27 | 28 |

You are logged in as {this.props.authentication.user.firstname}

29 |
30 | ) : ( 31 | 32 |

You are not authenticated.

33 |
34 | ); 35 | } 36 | } 37 | 38 | 39 | export default connect(state => state, actions)(Users); 40 | -------------------------------------------------------------------------------- /redux/actions/authActions.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router'; 2 | import axios from 'axios'; 3 | import { AUTHENTICATE, DEAUTHENTICATE, USER } from '../types'; 4 | import { API } from '../../config'; 5 | import { setCookie, removeCookie } from '../../utils/cookie'; 6 | 7 | // register user 8 | const register = ({ firstname, lastname, mobile_no, email_id, password, confirm_password }, type) => { 9 | if (type !== 'register') { 10 | throw new Error('Wrong API call!'); 11 | } 12 | return (dispatch) => { 13 | axios.post(`${API}/${type}`, {firstname, lastname, mobile_no, email_id, password, confirm_password }) 14 | .then((response) => { 15 | Router.push('/signin'); 16 | console.log(response.data.meta.message); 17 | }) 18 | .catch((err) => { 19 | switch (error.response.status) { 20 | case 422: 21 | alert(error.response.data.meta.message); 22 | break; 23 | case 401: 24 | alert(error.response.data.meta.message); 25 | break; 26 | case 500: 27 | alert('Interval server error! Try again!'); 28 | break; 29 | default: 30 | alert(error.response.data.meta.message); 31 | break; 32 | } 33 | }); 34 | }; 35 | }; 36 | // gets token from the api and stores it in the redux store and in cookie 37 | const authenticate = ({ email_id, password }, type) => { 38 | if (type !== 'login') { 39 | throw new Error('Wrong API call!'); 40 | } 41 | return (dispatch) => { 42 | console.log(email_id) 43 | axios.post(`${API}/${type}`, { email_id, password }) 44 | .then((response) => { 45 | console.log(response.data.data.user.token); 46 | setCookie('token', response.data.data.user.token); 47 | Router.push('/users'); 48 | dispatch({type: AUTHENTICATE, payload: response.data.data.user.token}); 49 | }) 50 | .catch((err) => { 51 | console.log(err); 52 | switch (error.response.status) { 53 | case 422: 54 | alert(error.response.data.meta.message); 55 | break; 56 | case 401: 57 | alert(error.response.data.meta.message); 58 | break; 59 | case 500: 60 | alert('Interval server error! Try again!'); 61 | break; 62 | default: 63 | alert(error.response.data.meta.message); 64 | break; 65 | } 66 | 67 | }); 68 | }; 69 | }; 70 | 71 | // gets the token from the cookie and saves it in the store 72 | const reauthenticate = (token) => { 73 | return (dispatch) => { 74 | dispatch({type: AUTHENTICATE, payload: token}); 75 | }; 76 | }; 77 | 78 | // removing the token 79 | const deauthenticate = () => { 80 | return (dispatch) => { 81 | removeCookie('token'); 82 | Router.push('/'); 83 | dispatch({type: DEAUTHENTICATE}); 84 | }; 85 | }; 86 | 87 | const getUser = ({ token }, type) => { 88 | console.log(token) 89 | return (dispatch) => { 90 | axios.get(`${API}/${type}`,{headers: { 91 | "Authorization" : "bearer " + token 92 | } 93 | }) 94 | .then((response) => { 95 | dispatch({ type: USER, payload: response.data.data.user }); 96 | }) 97 | .catch((error) => { 98 | switch (error.response.status) { 99 | case 401: 100 | Router.push('/'); 101 | break; 102 | case 422: 103 | alert(error.response.data.meta.message); 104 | break; 105 | case 500: 106 | alert('Interval server error! Try again!'); 107 | case 503: 108 | alert(error.response.data.meta.message); 109 | Router.push('/'); 110 | break; 111 | default: 112 | alert(error.response.data.meta.message); 113 | break; 114 | } 115 | }); 116 | }; 117 | }; 118 | 119 | 120 | export default { 121 | register, 122 | authenticate, 123 | reauthenticate, 124 | deauthenticate, 125 | getUser, 126 | }; -------------------------------------------------------------------------------- /redux/actions/index.js: -------------------------------------------------------------------------------- 1 | import authActions from './authActions'; 2 | 3 | // this is for those who have doubt regarding spread operator, this ...authActions is a spread it is use to import all the function from the file for example, it will import all the actions created in authActions file. 4 | //Which means, 5 | /* exports default { 6 | authenticate, 7 | reauthenticate, 8 | deauthenticate, 9 | } 10 | equals to. 11 | export default { 12 | ...authActions, 13 | };*/ 14 | 15 | export default { 16 | ...authActions, 17 | } 18 | -------------------------------------------------------------------------------- /redux/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import reducer from './reducers'; 4 | 5 | // in this file we are initializing the redux store by passing initial state and instance of reducer, we are applying thunk middleware to support async data flow. 6 | export const initStore = (initialState = {}) => { 7 | return createStore(reducer, initialState, applyMiddleware(thunk)); 8 | }; -------------------------------------------------------------------------------- /redux/reducers/authReducer.js: -------------------------------------------------------------------------------- 1 | import { AUTHENTICATE, DEAUTHENTICATE, USER } from '../types'; 2 | 3 | const initialState = { 4 | token: null, 5 | user: null, 6 | }; 7 | 8 | export default (state = initialState, action) => { 9 | switch (action.type) { 10 | case AUTHENTICATE: 11 | return Object.assign({}, state, { token: action.payload }); 12 | case USER: 13 | return Object.assign({}, state, { user: action.payload }); 14 | case DEAUTHENTICATE: 15 | return { token: null }; 16 | default: 17 | return state; 18 | } 19 | }; -------------------------------------------------------------------------------- /redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import authReducer from './authReducer'; 3 | 4 | const rootReducer = combineReducers({ 5 | authentication: authReducer, 6 | }); 7 | 8 | export default rootReducer; -------------------------------------------------------------------------------- /redux/types.js: -------------------------------------------------------------------------------- 1 | export const REGISTER = 'register'; 2 | export const AUTHENTICATE = 'authenticate'; 3 | export const DEAUTHENTICATE = 'deauthenticate'; 4 | export const USER = 'user'; -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhavikji/jwtauthenticationwithredux/8e4eb5c825c7f0e6c2fc831b3ae7df4c784ccec7/static/favicon.ico -------------------------------------------------------------------------------- /static/jwt_next_redux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhavikji/jwtauthenticationwithredux/8e4eb5c825c7f0e6c2fc831b3ae7df4c784ccec7/static/jwt_next_redux.jpg -------------------------------------------------------------------------------- /static/nextjs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bhavikji/jwtauthenticationwithredux/8e4eb5c825c7f0e6c2fc831b3ae7df4c784ccec7/static/nextjs.jpg -------------------------------------------------------------------------------- /utils/cookie.js: -------------------------------------------------------------------------------- 1 | // this file will have the task that we have to perform with cookies/ react-native you need to put your async storage code here. 2 | 3 | import cookie from 'js-cookie'; 4 | 5 | export const setCookie = (key, value) => { 6 | if (process.browser) { 7 | cookie.set(key, value, { 8 | expires: 1, 9 | path: '/' 10 | }); 11 | } 12 | }; 13 | 14 | export const removeCookie = (key) => { 15 | if (process.browser) { 16 | cookie.remove(key, { 17 | expires: 1 18 | }); 19 | } 20 | }; 21 | 22 | export const getCookie = (key, req) => { 23 | return process.browser 24 | ? getCookieFromBrowser(key) 25 | : getCookieFromServer(key, req); 26 | }; 27 | 28 | const getCookieFromBrowser = key => { 29 | return cookie.get(key); 30 | }; 31 | 32 | const getCookieFromServer = (key, req) => { 33 | if (!req.headers.cookie) { 34 | return undefined; 35 | } 36 | const rawCookie = req.headers.cookie 37 | .split(';') 38 | .find(c => c.trim().startsWith(`${key}=`)); 39 | if (!rawCookie) { 40 | return undefined; 41 | } 42 | return rawCookie.split('=')[1]; 43 | }; -------------------------------------------------------------------------------- /utils/initialize.js: -------------------------------------------------------------------------------- 1 | import Router from 'next/router'; 2 | import actions from '../redux/actions'; 3 | import { getCookie } from '../utils/cookie'; 4 | 5 | // checks if the page is being loaded on the server, and if so, get auth token from the cookie: 6 | export default function(ctx) { 7 | 8 | if(ctx.isServer) { 9 | if(ctx.req.headers.cookie) { 10 | ctx.store.dispatch(actions.reauthenticate(getCookie('token', ctx.req))); 11 | } 12 | } else { 13 | const token = ctx.store.getState().authentication.token; 14 | 15 | if(token && (ctx.pathname === '/signin' || ctx.pathname === '/signup')) { 16 | setTimeout(function() { 17 | Router.push('/'); 18 | }, 0); 19 | } 20 | } 21 | 22 | 23 | } --------------------------------------------------------------------------------