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