Next.js Authentication Tutorial

Next.js Authentication Tutorial

Learn how to add authentication to your Next.js application with Passport.

Learn how to add authentication to your Next.js application with Passport.

So you are thinking about developing your next great application with Next.js, huh? Or maybe you already started developing it and now you want to add authentication to your app. Either way, you are in the right place. If you are just starting to build your app with Next.js, now it is a good time to learn how to add authentication to it. If you already have something going on, it is never late to add authentication the right way.

In this article, you will learn how to leverage Passport to secure your Next.js app. What is cool about the approach you will learn here is that it even supports Server-Side Rendering (SSR) of protected routes. That is, with this setup, if an unknown user requests a secured route (those that are only accessible by authenticated users), your app will redirect them to the login page. On the other hand, if an authenticated user tries to load the same route, your Next.js app will recognize them and render the page on the server. Sounds cool? If so, keep reading!

This GitHub repository was created by the author while writing the article. You can use it as a reference if needed.## Prerequisites

To go through this tutorial, you will need Node.js and NPM installed. You should download and install them before continuing.

You'll also need some basic knowledge of React. You only need to know the basic scaffolding of components and JSX to follow along, but it will be difficult if you're a total beginner. If that's you, you can read our article on building and securing your first React app before continuing.

Scaffolding Next.js Applications

First thing you will need is a directory to put your project's source code. Also, you will have to transform this directory into an NPM project (if you prefer using Yarn, keep in mind you will have to adjust the commands accordingly). To achieve that, issue the following commands from a terminal:

# create a directory for your application
mkdir nextjs-passport

# move into it
cd nextjs-passport

# start it as an NPM project
npm init -y

After that, use the following command to add some dependencies to your new project:

npm i body-parser bootstrap dotenv \
  dotenv-webpack express isomorphic-fetch \
  next react react-bootstrap \
  react-dom styled-components

This command, as you can see, is installing a lot of dependencies. Some of them are required; some of then are optional. More specifically, these are the required dependencies:

  • <a href="https://github.com/expressjs/body-parser" target="_blank">body-parser</a>: This package lets your Express application parse different types of request bodies (e.g., application/json)
  • <a href="https://github.com/motdotla/dotenv" target="_blank">dotenv</a>: This package helps you read environment variables from a file.
  • <a href="https://github.com/mrsteele/dotenv-webpack" target="_blank">dotenv-webpack</a>: This package allows you to adjust some of the settings of Next.js.
  • <a href="https://github.com/expressjs/express" target="_blank">express</a>: This package will allow you to define a backend app more easily.
  • <a href="https://github.com/matthew-andrews/isomorphic-fetch" target="_blank">isomorphic-fetch</a>: This package adds an isomorphic fetch function that you can use both on the frontend and the backend.
  • <a href="https://github.com/zeit/next.js" target="_blank">next</a>, <a href="https://github.com/facebook/react" target="_blank">react</a>, and <a href="https://auth0.com/blog/next-js-authentication-tutorial/" target="_blank">react-dom</a>: Together, these packages let you build React apps that support server-side rendering.

While these are the optional ones:

  • <a href="https://github.com/twbs/bootstrap" target="_blank">bootstrap</a> and <a href="https://github.com/react-bootstrap/react-bootstrap" target="_blank">react-bootstrap</a>: These packages allow you to use Bootstrap components without having to use jQuery.
  • <a href="https://www.styled-components.com/" target="_blank">styled-components</a>: This package lets you use CSS in JavaScript.

Note: If you choose not to install the optional dependencies, you will have to refactor the code to your needs. Also, don't worry if the explanation above is vague. Soon, you will learn how the pieces work together.## Configuring Next.js Projects

After installing these dependencies, it's time to configure them. For starters, create a file called .babelrc inside the project root and add the following code to it:

// ./.babelrc

{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }]]
}

This file will make styled-components work with Next.js and let Babel know that you are developing with this package. Configuring Babel with the <a href="https://nextjs.org/blog/next-7#babel-7" target="_blank">next/babel</a> preset is needed to make this package compile your code accordingly.

With that in place, the next thing you will do is to create a file called .env inside the project root. For now, you will only use this file to define what port your app will listen to:

# ./.env

PORT=3000

In a few moments (while setting up authentication), you will add more properties to this file.

Now, you will create a file called next.config.js inside the project root, and you will add the following code to it:

// ./next.config.js

require("dotenv").config();

const path = require("path");
const Dotenv = require("dotenv-webpack");

module.exports = {
  webpack: config => {
    config.plugins = config.plugins || [];

    config.plugins = [
      ...config.plugins,

      // Read the .env file
      new Dotenv({
        path: path.join(__dirname, ".env"),
        systemvars: true
      })
    ];

    return config;
  }
};

The goal here is to help Next.js parse the configuration file you just created (i.e., the .env).

With that in place, create a directory called src inside the project root, then create three subdirectories inside it: components, pages, and state. To speed up, you can use the following commands to create these directories:

# the -p flag makes sure src is created first
mkdir -p src/components
mkdir src/pages
mkdir src/state

Next.js will use src/pages as the main directory to define what routes (pages) are available in your app.

After creating these directories, add a new file called index.js inside src/pages and add the following code to it:

// ./src/pages/index.js
import styled from "styled-components";

const Rocket = styled.div`
  text-align: center;
  img {
    width: 630px;
  }
`;

function Index() {
  return (
    <Rocket>
      <img src="https://media.giphy.com/media/QbumCX9HFFDQA/giphy.gif" />
    </Rocket>
  );
}

export default Index;

Next, you will create a file called _document.js inside src/pages and add the following code to it:

// ./src/pages/_document.js

import Document, { Head, Html, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head>
          <link
            rel="stylesheet"
            href="https://bootswatch.com/4/darkly/bootstrap.min.css"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

The code above defines a class with a static function called <a href="https://nextjs.org/docs#fetching-data-and-component-lifecycle" target="_blank">getInitialProps</a> that might look confusing. Don't worry about this method. Its purpose is to make Next.js capable of server-side rendering your styled component (i.e., you need this to make styled-components work properly on the server side). Besides that, this class defines the render method to be able to inject Bootstrap's stylesheet on the HTML that Next.js generates.

Note: By default, pages in Next.js skip the definition of the surrounding document's markup. That is, you will never include html and body tags on pages. To override the default structure, you need this <a href="https://nextjs.org/docs#custom-document" target="_blank">_document.js</a> file.
To wrap things in this section, open the package.json file and replace the scripts property with this:

// ./package.json

"scripts": {
  "dev": "next ./src"
},

Note: By default, Next.js will use the project root to look for your source code (e.g., the pages directory and its routes). However, this article uses the src directory to keep things more organized. As such, you need to add ./src to tell Next.js about this change. Feel free to move things around to better suit your needs.
After defining the dev script, you can execute npm run dev from your terminal to run the first version of your Next.js app. If everything works as expected, in a few seconds, you will be able to open <a href="http://localhost:3000/" target="_blank">http://localhost:3000/</a> in a web browser.

Developing Next.js Applications

In this section, you will develop a Next.js application that will allow users to do two things: share their thoughts (similar to what they do on Twitter) and consume the thoughts others shared. For the moment, these thoughts will have no information about the user who entered them (i.e., no identity management). In the next section though, you will add authentication to your app and will make it link thoughts to users.

Building custom APIs with Next.js

To support this application, you will need an API that serves thoughts when requested and that is capable of persisting new thoughts that users share. To build this API, the first thing you will do is to create a file called thoughts-api.js inside src; then you will add the following code to this file:

// ./src/thoughts-api.js

const bodyParser = require("body-parser");
const express = require("express");

const router = express.Router();

router.use(bodyParser.json());

const thoughts = [
  { _id: 123, message: "I love pepperoni pizza!", author: "unknown" },
  { _id: 456, message: "I'm watching Netflix.", author: "unknown" }
];

router.get("/api/thoughts", (req, res) => {
  const orderedThoughts = thoughts.sort((t1, t2) => t2._id - t1._id);
  res.send(orderedThoughts);
});

router.post("/api/thoughts", (req, res) => {
  const { message } = req.body;
  const newThought = {
    _id: new Date().getTime(),
    message,
    author: "unknown"
  };
  thoughts.push(newThought);
  res.send({ message: "Thanks!" });
});

module.exports = router;

This file is defining two Express endpoints. The first one allows clients to issue HTTP GET requests to get all thoughts. The second one will enable clients to issue HTTP POST requests to insert new thoughts.

After defining the endpoints, create a file called server.js inside src and add the following code to it:

// ./src/server.js

require("dotenv").config();
const express = require("express");
const http = require("http");
const next = require("next");
const thoughtsAPI = require("./thoughts-api");

const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
  dir: "./src"
});
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.use(thoughtsAPI);

  // handling everything else with Next.js
  server.get("*", handle);

  http.createServer(server).listen(process.env.PORT, () => {
    console.log(`listening on port ${process.env.PORT}`);
  });
});

This file is using Express to extend the default functionality that Next.js provides. After Next.js finishes preparing itself to serve requests (app.prepare().then), this file creates an Express server, adds to it the endpoints that thoughts-api.js defines, and starts listening to HTTP requests. Note that, apart from adding your custom endpoints to the Express server, this file is also stating that Next.js must handle all the other requests (i.e., the ones that are not seeking your custom endpoints). Without this setting, your Next.js app would end up serving only the custom API endpoints.

To wrap the custom API creation, open the package.json file and replace the scripts property with this:

// ./package.json

"scripts": {
  "dev": "node ./src/server.js",
  "build": "next build ./src",
  "start": "NODE_ENV=production node ./src/server.js"
},

Now, back in your terminal, issue npm run dev to confirm that everything is working as expected (you might need to stop the previous process first). After running your development server, you must be able to issue HTTP GET requests to <a href="http://localhost:3000/api/thoughts" target="_blank">http://localhost:3000/api/thoughts</a> to list thoughts.

Consuming custom APIs with Next.js

After adding a custom API to your Next.js application, the next thing you will do is to make your application consume the API. To do so, the first thing you will do is to create a component that will render the details of a single thought. So, create a file called Thought.js inside src/components and add the following code to it:

// ./src/components/Thought.js

import Card from "react-bootstrap/Card"

export default function Thought({ thought }) {
  const cardStyle = { marginTop: "15px" };
  return (
    <Card bg="secondary" text="white" style={cardStyle}>
      <Card.Body>
        <Card.Title>{thought.message}</Card.Title>
        <Card.Text>by {thought.author}</Card.Text>
      </Card.Body>
    </Card>
  );
}

This component will use the Card component that react-bootstrap provides to show the message entered by the user. As mentioned, thoughts won't hold information about their author yet. So, for the moment, they will all show "unknown" on the author property.

After creating this component, you will create a component that will render all thoughts. In this case, create a file called Thoughts.js inside src/components and add the following code to it:

// ./src/components/Thoughts.js

import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
import Thought from "./Thought";

export default function Thoughts(props) {
  return (
    <Row>
      <Col xs={12}>
        <h2>Latest Thoughts</h2>
      </Col>
      {props.thoughts &&
        props.thoughts.map(thought => (
          <Col key={thought._id} xs={12} sm={6} md={4} lg={3}>
            <Thought thought={thought} />
          </Col>
        ))}
      {!props.thoughts && <Col xs={12}>Loading...</Col>}
    </Row>
  );
}

As you can see, this component will get a list of thoughts through props and will iterate (thoughts.map) over them to create multiple Thought (now singular) elements. Just like the other component, this one takes advantage of React Bootstrap to render a nice user interface.

Lastly, you will open the index.js file and replace its code with this:

// ./src/pages/index.js

import Container from "react-bootstrap/Container";
import fetch from "isomorphic-fetch";
import Thoughts from "../components/Thoughts";

function Index(props) {
  return (
    <Container>
      <Thoughts thoughts={props.thoughts} />
    </Container>
  );
}

Index.getInitialProps = async ({ req }) => {
  const baseURL = req ? `${req.protocol}://${req.get("Host")}` : "";
  const res = await fetch(`${baseURL}/api/thoughts`);
  return {
    thoughts: await res.json()
  };
};

export default Index;

The new version of this file uses <a href="https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle" target="_blank">getInitialProps</a> (a feature that Next.js provides) to fetch thoughts from the custom API. By using this feature, you make Next.js render thoughts on the server side before issuing an HTML response to a client (usually a browser). Besides that, Next.js will also use this method when users trigger client-side navigation.

After creating these components, if you issue a request to <a href="http://localhost:3000" target="_blank">http://localhost:3000</a>, you will get an HTML response that includes all thoughts that the custom API return. To confirm that this is working, you can use any HTTP client to issue the request; then you can copy the HTML response and paste on a tool like HTML formatter to check the response more easily.

Feeding custom APIs with Next.js

After learning how to consume a custom API, the next thing you will do is to create a route where users will be able to share their thoughts. The idea here is to create a route that is protected and that only authenticated users can access. However, to make the authentication process easier to be digested, you will first create this route publicly; then, in the next section, you will secure the route and the endpoint that supports it.

As your users will have to navigate to this route, you need to enable them to do so. For that, you could simply add a link above the list of thoughts. However, to make this app more realistic, you will create a navigation bar that will include this link.

To create the navigation bar, create a file called Navbar.js inside src/components and add the following code to it:

// ./src/components/Navbar.js

import Link from "next/link";
import Container from "react-bootstrap/Container";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";

export default function AppNavbar() {
  const navbarStyle = { marginBottom: "25px" };
  return (
    <Navbar bg="light" expand="lg" style={navbarStyle}>
      <Container>
        <Navbar.Brand>
          <Link href="/">
            <a>Thoughts!</a>
          </Link>
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            <Link href="/share-thought">
              <a className="nav-link">New Thought</a>
            </Link>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

The component uses three React Bootstrap components (Container, Navbar, and Nav) to create a good-looking navigation bar. Now, as you don't want to include this bar inside each route manually, you will create a file called _app.js inside src/pages and add the following code to it:

// ./src/pages/_app.js

import React from "react";
import App, { Container as NextContainer } from "next/app";
import Head from "next/head";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import Navbar from "../components/Navbar";

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;

    return (
      <NextContainer>
        <Head>
          <title>Thoughts!</title>
        </Head>
        <Navbar />
        <Container>
          <Jumbotron>
            <Component {...pageProps} />
          </Jumbotron>
        </Container>
      </NextContainer>
    );
  }
}

export default MyApp;

By defining this file, you are extending the Next.js default <a href="https://nextjs.org/docs#custom-app" target="_blank">App</a> component behavior to add your Navbar on every page. Besides that, you are nesting the contents of your pages inside a Jumbotron, which will make your app look better.

After creating the navigation bar and extending the App component, you can finally define your new route. To implement this route, create a new file called share-thought.js inside src/pages and insert the following code into it:

// ./src/pages/share-thought.js

import Form from "react-bootstrap/Form";
import Router from "next/router";
import Button from "react-bootstrap/Button";
import Container from "react-bootstrap/Container";
const { useState } = require("react");

export default function ShareThought() {
  const [message, setMessage] = useState("");

  async function submit(event) {
    event.preventDefault();
    await fetch("/api/thoughts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        message
      })
    });
    Router.push("/");
  }

  return (
    <Container>
      <Form onSubmit={submit}>
        <Form.Group>
          <Form.Label>What is in your mind?</Form.Label>
          <Form.Control
            type="text"
            placeholder="Say something"
            onChange={e => setMessage(e.target.value)}
            value={message}
          />
        </Form.Group>
        <Button variant="primary" type="submit">
          Share
        </Button>
      </Form>
    </Container>
  );
}

This code might be a bit lengthy, but its behavior is somewhat easy to understand. First, after importing its dependencies, the ShareThought components starts by using the <a href="https://reactjs.org/docs/hooks-state.html" target="_blank">useState</a> hook. This feature allows developers to use state inside functional components. After that, the code defines a function called submit that the component will call when users click on the Share button. For now, this method simply issues a POST AJAX request to the backend API with the message that the user entered. Lastly, this code defines what the route will look like (i.e., defines what components React will use to put the view together).

With that in place, if you refresh your app on the browser (<a href="http://localhost:3000" target="_blank">http://localhost:3000</a>), you will see its new version. This version will show the navigation bar at the top and will allow users to navigate to the route where they will be able to share their thoughts.

Securing Next.js Applications with Passport

All right. After bootstrapping and developing the base application, you are now ready to learn how to secure your Next.js application with Passport. In this section, you will use Passport along with Auth0 to take advantage of the cool benefits we provide (e.g., out-of-the-box features like sign-in, sign-up, password reset, single sign-on, and multifactor authentication). However, if you prefer using another alternative, don't worry. Switching authentication strategies on Passport is easy and there are plenty of options available.

For starters, you will need an Auth0 application. If you don't have one yet, now it is a good time to sign up for a free Auth0 account. If you already have an account, you can reuse it.

After signing into the Auth0 dashboard, head to the Applications section and click on Create Application. Clicking on this button will make Auth0 show a dialog where you will have to fill the following options:

  • Name: In this field, you can type something meaningful and easily identifiable. For example: "Next.js and Passport"
  • Type: Here, you will have to choose Regular Web Application.

After filling this form, click on Create. Doing so will make Auth0 redirect you to the Quick Start section of your new application. From there, click on Settings and update your Auth0 application as follows:

  • Allowed Callback URLs: <a href="http://localhost:3000/callback" target="_blank">http://localhost:3000/callback</a>
  • Allowed Logout URLs: <a href="http://localhost:3000" target="_blank">http://localhost:3000</a>

You will need this configuration because Auth0 will only redirect users (after a successful login or logout process) to the URLs whitelisted in these fields. So, scroll to the bottom of the page and click on Save Changes. Don't close this page yet, you will need to copy some values from it soon.

Now, head back to your terminal, stop the development server (Ctrl + C), and execute the following command:

npm install passport passport-auth0 express-session uid-safe

This will make NPM install four packages that you will need for a proper setup:

  • <a href="https://github.com/jaredhanson/passport#readme" target="_blank">passport</a>: This is the main Passport package.
  • <a href="https://github.com/auth0/passport-auth0#readme" target="_blank">passport-auth0</a>: This is the Auth0 strategy for Passport.
  • <a href="https://github.com/expressjs/session#readme" target="_blank">express-session</a>: This is the official Express middleware for session handling.
  • <a href="https://github.com/crypto-utils/uid-safe#readme" target="_blank">uid-safe</a>: A package that helps creating safe UIDs for URLs and cookies.

After installing these dependencies, open the server.js file and replace its contents with this:

// ./src/server.js

require("dotenv").config();
const express = require("express");
const http = require("http");
const next = require("next");
const session = require("express-session");
// 1 - importing dependencies
const passport = require("passport");
const Auth0Strategy = require("passport-auth0");
const uid = require('uid-safe');
const authRoutes = require("./auth-routes");
const thoughtsAPI = require("./thoughts-api");

const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
  dir: "./src"
});
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  // 2 - add session management to Express
  const sessionConfig = {
    secret: uid.sync(18),
    cookie: {
      maxAge: 86400 * 1000 // 24 hours in milliseconds
    },
    resave: false,
    saveUninitialized: true
  };
  server.use(session(sessionConfig));

  // 3 - configuring Auth0Strategy
  const auth0Strategy = new Auth0Strategy(
    {
      domain: process.env.AUTH0_DOMAIN,
      clientID: process.env.AUTH0_CLIENT_ID,
      clientSecret: process.env.AUTH0_CLIENT_SECRET,
      callbackURL: process.env.AUTH0_CALLBACK_URL
    },
    function(accessToken, refreshToken, extraParams, profile, done) {
      return done(null, profile);
    }
  );

  // 4 - configuring Passport
  passport.use(auth0Strategy);
  passport.serializeUser((user, done) => done(null, user));
  passport.deserializeUser((user, done) => done(null, user));

  // 5 - adding Passport and authentication routes
  server.use(passport.initialize());
  server.use(passport.session());
  server.use(authRoutes);

  server.use(thoughtsAPI);

  // 6 - you are restricting access to some routes
  const restrictAccess = (req, res, next) => {
    if (!req.isAuthenticated()) return res.redirect("/login");
    next();
  };

  server.use("/profile", restrictAccess);
  server.use("/share-thought", restrictAccess);

  // handling everything else with Next.js
  server.get("*", handle);

  http.createServer(server).listen(process.env.PORT, () => {
    console.log(`listening on port ${process.env.PORT}`);
  });
});

The new version of this file adds six important changes to your custom API. The following list summarizes these changes:

  1. You are importing five new modules. One for each of the four dependencies you just installed, and one for a module called auth-routes. This module (which you will create soon) will define the authentication routes.
  2. You are making your Express server use the session package (server.use(session(sessionConfig))) to persist user sessions. Note that, through the <a href="https://github.com/expressjs/session#readme" target="_blank">sessionConfig</a> object, you are defining how this package will behave (e.g., you are using uid-safe to generate a safe secret to hash your cookies, and you are defining that cookies will stay valid for 24 hours).
  3. You are configuring the Auth0Strategy with four environment variables. You will learn how to set them later.
  4. You are configuring Passport to use Auth0's strategy, and you are telling this package what data it should keep on users sessions.
  5. ou are making your Express server use Passport, and you are adding the authentication routes to it.
  6. ou are restricting access to the /profile and the /share-thought routes.

After refactoring your Express server to use Passport, you will need to create a file called auth-routes.js inside src. In this file, you will add the following code to define the authentication routes:

// ./src/auth-routes.js

const express = require("express");
const passport = require("passport");

const router = express.Router();

router.get("/login", passport.authenticate("auth0", {
  scope: "openid email profile"
}), (req, res) => res.redirect("/"));

router.get("/callback", (req, res, next) => {
  passport.authenticate("auth0",  (err, user) => {
    if (err) return next(err);
    if (!user) return res.redirect("/login");
    req.logIn(user, (err) => {
      if (err) return next(err);
      res.redirect("/");
    });
  })(req, res, next);
});

router.get("/logout", (req, res) => {
  req.logout();

  const {AUTH0_DOMAIN, AUTH0_CLIENT_ID, BASE_URL} = process.env;
  res.redirect(`https://${AUTH0_DOMAIN}/logout?client_id=${AUTH0_CLIENT_ID}&returnTo=${BASE_URL}`);
});

module.exports = router;

As you can see, your application will have to support three new routes:

  • /login: When users reach this route, your app will call Passport's authentication method telling it to use Auth0's strategy. This will make your app redirect users to Auth0's Universal Login Page.
  • /callback: After authenticating, Auth0 will redirect users to this URL. There, your app will get a code back from the authentication process and will use it (along with the AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET environment variables) to exchange for user details (a profile, mainly).
  • /logout: When your users want to log out from the application, they will request this URL. This endpoint will log users out from your app and from Auth0 as well.

Then, you can open the .env file and update it as follows:

# ./.env

# ... keep PORT untouched ...

AUTH0_DOMAIN=...
AUTH0_CLIENT_ID=...
AUTH0_CLIENT_SECRET=...
AUTH0_CALLBACK_URL=http://localhost:3000/callback
BASE_URL=http://localhost:3000

To replace the AUTH0_DOMAIN, AUTH0_CLIENT_ID, and AUTH0_CLIENT_SECRET variables, you can copy the Domain, Client ID, and Client Secret values from the Settings section of your Auth0 Application.

Note: Don't push these values to a version control system like Git (mainly AUTH0_CLIENT_SECRET, which should be kept private). .env files are designed to keep environment variables that are specific to an environment (in this case, your machine) and should not be shared.
Next, you will open the thoughts-api.js file and will replace the POST method of the /api/thoughts endpoint with this:

// ./src/thoughts-api.js

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) return next();
  res.send(401);
}

router.post("/api/thoughts", ensureAuthenticated, (req, res) => {
  const { message } = req.body;
  const newThought = {
    _id: new Date().getTime(),
    message,
    author: req.user.displayName
  };
  thoughts.push(newThought);
  res.send({ message: "Thanks!" });
});

This will make your endpoint respond with an HTTP 401 unauthorized status to unauthenticated users. Authenticated users will still be able to use this endpoint.

With that in place, you can open the Navbar.js file and replace its contents with this:

// ./src/components/Navbar.js

import Link from "next/link";
import Container from "react-bootstrap/Container";
import Navbar from "react-bootstrap/Navbar";
import Nav from "react-bootstrap/Nav";

export default function AppNavbar({ user }) {
  const navbarStyle = { marginBottom: "25px" };
  return (
    <Navbar bg="light" expand="lg" style={navbarStyle}>
      <Container>
        <Navbar.Brand>
          <Link href="/">
            <a>Thoughts!</a>
          </Link>
        </Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="mr-auto">
            {user && (
              <>
                <Link href="/share-thought">
                  <a className="nav-link">New Thought</a>
                </Link>
                <Link href="/profile">
                  <a className="nav-link">Profile</a>
                </Link>
                <Link href="/logout">
                  <a className="nav-link">Log Out</a>
                </Link>
              </>
            )}
            {!user && (
              <Link href="/login">
                <a className="nav-link">Log In</a>
              </Link>
            )}
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

As you can see, this version of your navigation bar will receive a user props and will use it to decide if it must show the login button, or if it shows the logout button and a button to redirect users to a profile page.

This profile page is a page where your users will be able to see what your app knows about them. To define it, create a file called profile.js inside src/pages and add the following code to it:

// ./src/pages/profile.js

import styled from "styled-components";

const Picture = styled.img`
  border-radius: 50%;
  border: 3px solid white;
  width: 100px;
`;

function Profile({ user }) {
  return (
    <div>
      <h2>
        <Picture src={user.picture} alt={user.displayName} /> Hello, {user.displayName}
      </h2>
      <p>This is what we know about you:</p>
      <ul>
        { Object.keys(user).map(key => (
          <li key={key}>{key}: {user[key].toString()}</li>
        ))}
      </ul>
    </div>
  );
}

export default Profile;

As you can see, this page will mainly show users' displayName and picture. Then, it will iterate over the properties of the user object to create an unordered list (ul) with them and their values.

To complete this section, open the _app.js file and replace its contents with this:

// ./src/pages/_app.js

import React from "react";
import App, { Container as NextContainer } from "next/app";
import Head from "next/head";
import Container from "react-bootstrap/Container";
import Jumbotron from "react-bootstrap/Jumbotron";
import Navbar from "../components/Navbar";

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }
    if (ctx.req && ctx.req.session.passport) {
      pageProps.user = ctx.req.session.passport.user;
    }
    return { pageProps };
  }

  constructor(props) {
    super(props);
    this.state = {
      user: props.pageProps.user
    };
  }

  render() {
    const { Component, pageProps } = this.props;

    const props = {
      ...pageProps,
      user: this.state.user,
    };

    return (
      <NextContainer>
        <Head>
          <title>Thoughts!</title>
        </Head>
        <Navbar user={this.state.user} />
        <Container>
          <Jumbotron>
            <Component {...props} />
          </Jumbotron>
        </Container>
      </NextContainer>
    );
  }
}

export default MyApp;

One important change in this file is that you are redefining the static getInitialProps method of the MyApp class. You are doing this to add the user object (if available) to all pages' props. That is, the Profile page you just defined will get this props. Also, ShareThought and Index will get this data too (the later will still get thoughts alongside the userobject).

Besides that, you are making the MyApp component hold the user that comes from the backend on its internal state. This will make any client-side navigation have access to the user profile.

With the changes that this section introduces, you are ready to take your secure app for a spin. So, in your terminal, run the npm run dev command. Then, head to <a href="http://localhost:3000" target="_blank">http://localhost:3000</a> and use the login button to test the authentication process.

If everything works as expected, after using the Auth0 login page to sign up (or sign in, in case you already have a user), you will be redirected back to your app. There, you will see the navigation bar showing three links: New Thought, Profile, and Log Out. Clicking on the second link will redirect you to the page where you will see your own information.

What is more interesting is that, at this moment, the view is rendered by the browser (i.e., without the help of the backend). And, if you use your browser to refresh this page, the same view appears, but now rendered with the help of the backend (i.e., the view travels through the wire with your profile data already structure in the HTML DOM).

Recap

In this article, you learned how to secure Next.js applications with Passport properly. You also learned how to use Auth0 as a strategy in your Passport configuration. This strategy speeds up the process because it prevents you from having to invest time in developing features like sign-in, sign-up, password reset, and similar.

As homework, you can try replacing Auth0 strategy with another one. But be aware, if you decide to handle user credentials by yourself, you will have to take care of important things like password hashing and multi-factor authentication manually. Or, if you end up deciding to support social strategies (like Facebook and Google), you will have to install each one separately (while with Auth0 you can add new social provider with a single click).

Introduction New Features in TypeScript 3.7 and How to Use Them

Introduction New Features in TypeScript 3.7 and How to Use Them

The TypeScript 3.7 release is coming soon, and it's going to be a big one.

The target release date is November 5th, and there are some seriously exciting headline features included:

  • Assert signatures.
  • Recursive type aliases.
  • Top-level await.
  • Null coalescing.
  • Optional chaining.

Personally, I'm super excited about this, they're going to whisk away all sorts of annoyances that I've been fighting in TypeScript while building HTTP Toolkit.

If you haven't been paying close attention to the TypeScript development process though, it's probably not clear what half of these mean, or why you should care. Let's talk through all of them.

Assert Signatures

This is a brand-new and little-known TypeScript feature, which allows you to write functions that act like type guards as a side-effect, rather than explicitly returning their boolean result.

It's easiest to demonstrate this with a JavaScript example:

function assertString(input) { 
  if (typeof input === 'string') 
    return; 
  else 
    throw new Error('Input must be a string!'); 
} 
function doSomething(input) { 
  assertString(input); 
  // ... Use input, confident that it's a string 
} 
doSomething('abc'); 
// All good doSomething(123); // Throws an error

This pattern is neat and useful, and you can't use it in TypeScript today.

TypeScript can't know that you've guaranteed the type of input after it's run assertString. Typically, people just make the argument input: string to avoid this, and that's good. But, it also just pushes the type checking problem somewhere else, and in cases where you just want to fail hard, it's useful to have this option available.

Fortunately, soon we will:

// With TS 3.7 
function assertString(input: any): 
	asserts input is string { 
      // <-- the magic 
      if (typeof input === 'string') 
        return; 
      else 
        throw new Error('Input must be a string!'); 
    } 
function doSomething(input: string | number) { 
  assertString(input); 
  // input's type is just 'string' here }

Here assert input is string means that if this function ever returns, TypeScript can narrow the type of input to string, just as if it was inside an if block with a type guard.

To make this safe, that means if the assert statement isn't true then your assert function must either throw an error or not return at all (kill the process, infinite loop, you name it).

That's the basics, but this actually lets you pull some really neat tricks:

// With TS 3.7 
// Asserts that input is truthy, throwing immediately if not: 
function assert(input: any): 
	asserts input { // <-- not a typo 
      if (!input) 
        throw new Error('Not a truthy value'); 
    } 
declare const x: number | string | undefined; 
assert(x); // Narrows x to number | string 
// Also usable with type guarding expressions! 
assert(typeof x === 'string'); 
// Narrows x to string // -- Or use assert in your tests: -- 
const a: Result | Error = doSomethingTestable(); 
expect(a).is.instanceOf(result); 
// 'instanceOf' could 'asserts a is Result' 
expect(a.resultValue).to.equal(123); 
// a.resultValue is now legal // -- Use as a safer ! that throws immediately if 
// you're wrong -- 
function assertDefined<T>(obj: T): 
	asserts obj is NonNullable<T> { 
      if (obj === undefined || obj === null) { 
        throw new Error('Must not be a nullable value'); 
      } 
    } 
declare const x: string | undefined; 
// Gives y just 'string' as a type, could throw elsewhere later: 
const y = x!; 
// Gives y 'string' as a type, or throws immediately if you're wrong: 
assertDefined(x); const z = x; 
// -- Or even update types to track a function's side-effects -- 
type X<T extends string | {}> = { value: T }; 
// Use asserts to narrow types according to side effects: 
function setX<T extends string | {}>(x: X<any>, v: T): 
	asserts x is X<T> { 
      x.value = v; 
    } 
	declare let x: X<any>; 
// x is now { value: any }; 
setX(x, 123); 
// x is now { value: number };

This is still in flux, so don't take it as the definite result, and keep an eye on the pull request if you want the final details.

There's even a discussion there about allowing functions to assert something and return a type, which would let you extend the final example above to track a much wider variety of side effects, but we'll have to wait and see how that plays out.

Top-Level Await

Async/await is amazing and makes promises dramatically cleaner to use.

Unfortunately, though, you can't use them at the top level. This might not be something you care about much in a TS library or application, but if you're writing a runnable script or using TypeScript in a REPL, then this gets super annoying.

It's even worse if you're used to frontend development, since top-level await has been working nicely in the Chrome and Firefox console for a couple of years now.

Fortunately though, a fix is coming. This is actually a general stage-3 JS proposal, so it'll be everywhere else eventually too, but for TS devs 3.7 is where the magic happens.

This one's simple, but let's have another quick demo anyway:


// Your only solution right now for a script that does something async: 
async function doEverything() { 
  ... 
  const response = await fetch('http://example.com'); 
  ... 
} 
  
doEverything(); // <- eugh (could use an IIFE instead, but even more eugh)

With top-level await:

// With TS 3.7: 
// Your script: ... 
const response = await fetch('http://example.com'); 
// ...

There's a notable gotcha here: if you're not writing a script, or using a REPL, don't write this at the top level, unless you really know what you're doing!

It's totally possible to use this to write modules that do blocking async steps when imported. That can be useful for some niche cases, but people tend to assume that their import statement is a synchronous, reliable, and fairly quick operation, and you could easily hose your codebase's startup time if you start blocking imports for complex async processes (even worse, processes that can fail).

This is somewhat mitigated by the semantics of imports of async modules: they're imported and run in parallel, so the importing module effectively waits for Promise.all(importedModules) before being executed.

Rich Harris wrote an excellent piece on a previous version of this spec before that change when imports ran sequentially and this problem was much worse), which makes for good background reading on the risks here if you're interested.

It's also worth noting that this is only useful for module systems that support asynchronous imports. There isn't yet a formal spec for how TS will handle this, but that likely means that a very recent target configuration, and, either ES Modules or Webpack v5 (whose alphas have experimental support), will be used at runtime.

Recursive Type Aliases

If you're ever tried to define a recursive type in TypeScript, you may have run into StackOverflow questions like this: https://stackoverflow.com/questions/47842266/recursive-types-in-typescript.

Right now, you can't. Interfaces can be recursive, but there are limitations to their expressiveness, and type aliases can't. That means right now, you need to combine the two: define a type alias and extract the recursive parts of the type into interfaces. It works, but it's messy, and we can do better.

As a concrete example, this is the suggested type definition for JSON data:

type JSONValue = | string | number | boolean | JSONObject | JSONArray; 
interface JSONObject { [x: string]: JSONValue; } 
interface JSONArray extends Array<JSONValue> { }

That works, but the extra interfaces are only there because they're required to get around the recursion limitation.

Fixing this requires no new syntax; it just removes that restriction, so the below compiles:

// With TS 3.7: 
type JSONValue = | string | number | boolean | { [x: string]: JSONValue } | Array<JSONValue>;

Right now, that fails to compile with Type alias 'JSONValue' circularly references itself. Soon though, soon...

Null Coalescing

Aside from being difficult to spell, this one is quite simple and easy. It's based on a JavaScript stage-3 proposal, which means it'll also be coming to your favorite vanilla JavaScript environment soon (if it hasn't already).

In JavaScript, there's a common pattern for handling default values, and falling back to the first valid result of a defined group. It looks something like this:

// Use the first of firstResult/secondResult which is truthy: 
const result = firstResult || secondResult; 
// Use configValue from provided options if truthy, or 'default' if not: 
this.configValue = options.configValue || 'default';

This is useful in a host of cases, but due to some interesting quirks in JavaScript, it can catch you out. If firstResult or options.configValue can meaningfully be set to false, an empty string or 0, then this code has a bug. If those values are set, then when considered as booleans they're falsy, so the fallback value (secondResult / 'default') is used anyway.

Null coalescing fixes this. Instead of the above, you'll be able to write:

// With TS 3.7: 
// Use the first of firstResult/secondResult which is *defined*: 
const result = firstResult ?? secondResult; 
// Use configSetting from provided options if *defined*, or 'default' if not: 
this.configValue = options.configValue ?? 'default';

?? differs from || in that it falls through to the next value only if the first argument is null or undefined, not falsy. That fixes our bug. If you pass false as firstResult, that will be used instead of secondResult because, while it's falsy, it is still defined, and that's all that's required.

It's simple but super-useful, as it takes away a whole class of bugs.

Optional Chaining

Last but not least, optional chaining is another stage-3 proposal that is making its way into TypeScript.

This is designed to solve an issue faced by developers in every language: how do you get data out of a data structure when some or all of it might not be present?

Right now, you might do something like this:

// To get data.key1.key2, if any level could be null/undefined: 
let result = data ? (data.key1 ? data.key1.key2 : undefined) : undefined; 
// Another equivalent alternative: 
let result = ((data || {}).key1 || {}).key2;

Nasty! This gets much much worse if you need to go deeper, and although the second example works at runtime, it won't even compile in TypeScript, since the first step could be {}, in which case key1 isn't a valid key at all.

This gets still more complicated if you're trying to get into an array, or there's a function call somewhere in this process.

There's a host of other approaches to this, but they're all noisy, messy & error-prone. With optional chaining, you can do this:

// With TS 3.7: 
// Returns the value is it's all defined & non-null, or undefined if not. 
let result = data?.key1?.key2; 
// The same, through an array index or property, if possible: 
array?.[0]?.['key']; 
// Call a method, but only if it's defined: 
obj.method?.(); 
// Get a property, or return 'default' if any step is not defined: 
let result = data?.key1?.key2 ?? 'default';

The last case shows how neatly some of these dovetails together: null coalescing + optional chaining is a match made in heaven.

One gotcha: this will return undefined for missing values, even if they were null, e.g. in cases like (null)?.key (returns undefined). A small point, but one to watch out for if you have a lot of null in your data structures.

That's the lot! That should outline all the essentials for these features, but there are lots of smaller improvements, fixes, and editor support improvements coming too, so take a look at the official roadmap if you want to get into the nitty-gritty.

Angular 8 Pagination Example and Tutorial

Angular 8 Pagination Example and Tutorial

Pagination is the best way to show huge number of records in series for any application. Also showing/fetching thousands of record at one go will affect the performance of the application.

Pagination is the best way to show huge number of records in series for any application. Also showing/fetching thousands of record at one go will affect the performance of the application.

For example, when you search something that returns a large number of records which cannot be shown on a single web page therefore, those records are part into number of pages that can be accessed through links via pagination structure.

So today in this demo we will discuss the simple pagination in Angular 8.

Step 1: Create a basic app with angular cli
ng new angular8-simple-pagination-example

By typing the above command we will see a basic angular app created on the current folder. So move to the created folder by typing **cd angular8-simple-pagination-example/. **You can check the newly created app by typing http://localhost:4200 on the browser.

Step 2: install ngx-pagination pagination dependency from terminal

So run the below command over terminal

npm install ngx-pagination --save

Step 3: Create dummy records for pagination

Now we will create static data to show the pagination. So lets have a look on the code under file **app.component.ts **

import { Component } from '@angular/core';
import {NgxPaginationModule} from 'ngx-pagination';
@Component({
   selector: 'app-root',
   templateUrl: './app.component.html',
   styleUrls: ['./app.component.css']
})
export class AppComponent {
   title = 'simple pagination demo';
   collection = [];
   constructor(){
     for(let i=1;i<=100;i++){
       let Obj = {'name': `Employee Name ${i}`,'code': `EMP00 ${i}`}
       this.collection.push(Obj);
     }
   }
}

In the above file, we can see that inside constructor we have created a loop for created dummy record for 100 employees having employee name & code for showing pagination.

Step 4: Import dependency in app.module.ts

Now let's have a look on the code inside **app.module.ts **where the ngx-pagination module has been imported

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { NgxPaginationModule } from 'ngx-pagination';
import { AppComponent } from './app.component';
 
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
NgxPaginationModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Step 5: Update view from app.component.html

Now one last step needed to do is, add the below code anywhere inside app.component.html

*  Emp Name | Emp code
 {{item.name}} | {{item.code}} 


Now, we are done with all the needed steps for the pagination in our angular application.

Step 6: Run the app

Run the app over the terminal with npm start and check the app after typing the url http://localhost:4200/.** **A page will open like below:

Conclusion

By following these easy steps we can easily achieve the client side pagination in Angular 8 application. If you want to impliment server side pagination in angular8 Server Side Pagination in Angular Example and Tutorial . You can also find other demos of Angular Sample Application here to start working on enterprise level application. Click here to view more about the pagination package over npm.

Create Simple Login Page using Angular 8 and HTTP Authentication

Create Simple Login Page using Angular 8 and HTTP Authentication

In this article, you'll learn how to setup a simple login page using Angular 8 and Basic HTTP authentication

In this article, you'll learn how to setup a simple login page using Angular 8 and Basic HTTP authentication

Tutorial built with Angular 8.0.2 and the Angular CLI

Angular CLI was used to generate the base project structure with the ng new command, the CLI is also used to build and serve the application. For more info about the Angular CLI see https://angular.io/cli.

Styling of the example app is all done with Bootstrap 4.3, for more info about Bootstrap see https://getbootstrap.com/docs/4.3/getting-started/introduction/.

Running the Angular 8 Basic Authentication Tutorial Example Locally
  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the tutorial project source code from https://github.com/cornflourblue/angular-8-basic-authentication-example
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the
  5. command line in the project root folder, this will build the application
  6. and automatically launch it in the browser on the URL
  7. http://localhost:4200.

NOTE: You can also run the app directly using the Angular CLI command ng serve --open. To do this first install the Angular CLI globally on your system with the command npm install -g @angular/cli.

Running the Tutorial Example with a Real Backend API

The Angular 8 basic authentication example app uses a fake / mock backend by default so it can run in the browser without a real api, to switch to a real backend api you just have to remove or comment out the line below the comment // provider used to create fake backend located in the /src/app/app.module.ts file.

Angular 8 Tutorial Project Structure

The app and code structure of the tutorial mostly follows the best practice recommendations in the official Angular Style Guide, with a few of my own tweaks here and there.

Each feature has it's own folder (home & login), other shared/common code such as services, models, helpers etc are placed in folders prefixed with an underscore _ to easily differentiate them and group them together at the top of the folder structure.

The index.ts files in each folder are barrel files that group the exported modules from a folder together so they can be imported using the folder path instead of the full module path and to enable importing multiple modules in a single import (e.g. import { AuthenticationService, UserService } from '../_services').

Path aliases @app and @environments have been configured in tsconfig.json that map to the /src/app and /src/environments directories. This allows imports to be relative to the app and environments folders by prefixing import paths with aliases instead of having to use long relative paths (e.g. import MyComponent from '../../../MyComponent').

Here are the main project files that contain the application logic, I left out some files that were generated by Angular CLI ng new command that I didn't change.

  • src
  • app
  • _helpers
  • auth.guard.ts
  • basic-auth.interceptor.ts
  • error.interceptor.ts
  • fake-backend.ts
  • index.ts
  • _models
  • user.ts
  • index.ts
  • _services
  • authentication.service.ts
  • user.service.ts
  • index.ts
  • home
  • home.component.html
  • home.component.ts
  • index.ts
  • login
  • login.component.html
  • login.component.ts
  • index.ts
  • app.component.html
  • app.component.ts
  • app.module.ts
  • app.routing.ts
  • environments
  • environment.prod.ts
  • environment.ts
  • index.html
  • main.ts
  • polyfills.ts
  • styles.less
  • package.json
  • tsconfig.json
Auth Guard

Path: /src/app/_helpers/auth.guard.ts

The auth guard is an angular route guard that's used to prevent unauthenticated users from accessing restricted routes, it does this by implementing the CanActivate interface which allows the guard to decide if a route can be activated with the canActivate() method. If the method returns true the route is activated (allowed to proceed), otherwise if the method returns false the route is blocked.

The auth guard uses the authentication service to check if the user is logged in, if they are logged in it returns true from the canActivate() method, otherwise it returns false and redirects the user to the login page.

Angular route guards are attached to routes in the router config, this auth guard is used in app.routing.ts to protect the home page route.

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

import { AuthenticationService } from '@app/_services';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
    constructor(
        private router: Router,
        private authenticationService: AuthenticationService
    ) { }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const currentUser = this.authenticationService.currentUserValue;
        if (currentUser) {
            // logged in so return true
            return true;
        }

        // not logged in so redirect to login page with the return url
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
        return false;
    }
}
Basic Authentication Interceptor

Path: /src/app/_helpers/basic-auth.interceptor.ts

The Basic Authentication Interceptor intercepts http requests from the application to add basic authentication credentials to the Authorization header if the user is logged in.

It's implemented using the HttpInterceptor class included in the HttpClientModule, by extending the HttpInterceptor class you can create a custom interceptor to modify http requests before they get sent to the server.

Http interceptors are added to the request pipeline in the providers section of the _app.module.ts_ file.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';

import { AuthenticationService } from '@app/_services';

@Injectable()
export class BasicAuthInterceptor implements HttpInterceptor {
    constructor(private authenticationService: AuthenticationService) { }

    intercept(request: HttpRequest, next: HttpHandler): Observable> {
        // add authorization header with basic auth credentials if available
        const currentUser = this.authenticationService.currentUserValue;
        if (currentUser && currentUser.authdata) {
            request = request.clone({
                setHeaders: { 
                    Authorization: `Basic ${currentUser.authdata}`
                }
            });
        }

        return next.handle(request);
    }
}
Http Error Interceptor

Path: /src/app/_helpers/error.interceptor.ts

The Error Interceptor intercepts http responses from the api to check if there were any errors. If there is a 401 Unauthorized response the user is automatically logged out of the application, all other errors are re-thrown up to the calling service so an alert with the error can be displayed on the screen.

It's implemented using the HttpInterceptor class included in the HttpClientModule, by extending the HttpInterceptor class you can create a custom interceptor to catch all error responses from the server in a single location.

Http interceptors are added to the request pipeline in the providers section of the app.module.ts file.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

import { AuthenticationService } from '@app/_services';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
    constructor(private authenticationService: AuthenticationService) { }

    intercept(request: HttpRequest, next: HttpHandler): Observable> {
        return next.handle(request).pipe(catchError(err => {
            if (err.status === 401) {
                // auto logout if 401 response returned from api
                this.authenticationService.logout();
                location.reload(true);
            }

            const error = err.error.message || err.statusText;
            return throwError(error);
        }))
    }
}
Fake Backend Provider

Path: /src/app/_helpers/fake-backend.ts

In order to run and test the Angular application without a real backend API, the example uses a fake backend that intercepts the HTTP requests from the Angular app and send back "fake" responses. This is done by a class that implements the Angular HttpInterceptor interface, for more information on Angular HTTP Interceptors see https://angular.io/api/common/http/HttpInterceptor

The fake backend contains a handleRoute function that checks if the request matches one of the faked routes in the switch statement, at the moment this includes POST requests to the /users/authenticate route for handling authentication, and GET requests to the /users route for getting all users.

Requests to the authenticate route are handled by the authenticate() function which checks the username and password against an array of hardcoded users. If the username and password are correct then an ok response is returned with the user details, otherwise an error response is returned.

Requests to the get users route are handled by the getUsers() function which checks if the user is logged in by calling the new isLoggedIn() helper function. If the user is logged in an ok() response with the whole users array is returned, otherwise a 401 Unauthorized response is returned by calling the new unauthorized() helper function.

If the request doesn't match any of the faked routes it is passed through as a real HTTP request to the backend API.

import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';

import { User } from '@app/_models';

const users: User[] = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];

@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor {
    intercept(request: HttpRequest, next: HttpHandler): Observable> {
        const { url, method, headers, body } = request;

        // wrap in delayed observable to simulate server api call
        return of(null)
            .pipe(mergeMap(handleRoute))
            .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
            .pipe(delay(500))
            .pipe(dematerialize());

        function handleRoute() {
            switch (true) {
                case url.endsWith('/users/authenticate') && method === 'POST':
                    return authenticate();
                case url.endsWith('/users') && method === 'GET':
                    return getUsers();
                default:
                    // pass through any requests not handled above
                    return next.handle(request);
            }    
        }

        // route functions

        function authenticate() {
            const { username, password } = body;
            const user = users.find(x => x.username === username && x.password === password);
            if (!user) return error('Username or password is incorrect');
            return ok({
                id: user.id,
                username: user.username,
                firstName: user.firstName,
                lastName: user.lastName
            })
        }

        function getUsers() {
            if (!isLoggedIn()) return unauthorized();
            return ok(users);
        }

        // helper functions

        function ok(body?) {
            return of(new HttpResponse({ status: 200, body }))
        }

        function error(message) {
            return throwError({ error: { message } });
        }

        function unauthorized() {
            return throwError({ status: 401, error: { message: 'Unauthorised' } });
        }

        function isLoggedIn() {
            return headers.get('Authorization') === `Basic ${window.btoa('test:test')}`;
        }
    }
}

export let fakeBackendProvider = {
    // use fake backend in place of Http service for backend-less development
    provide: HTTP_INTERCEPTORS,
    useClass: FakeBackendInterceptor,
    multi: true
};
User Model

Path: /src/app/_models/user.ts

The user model is a small class that defines the properties of a user.

export class User {
    id: number;
    username: string;
    password: string;
    firstName: string;
    lastName: string;
    authdata?: string;
}
Authentication Service

Path: /src/app/_services/authentication.service.ts

The authentication service is used to login & logout of the Angular app, it notifies other components when the user logs in & out, and allows access the currently logged in user.

RxJS Subjects and Observables are used to store the current user object and notify other components when the user logs in and out of the app. Angular components can subscribe() to the public currentUser: Observable property to be notified of changes, and notifications are sent when the this.currentUserSubject.next() method is called in the login() and logout() methods, passing the argument to each subscriber. The RxJS BehaviorSubject is a special type of Subject that keeps hold of the current value and emits it to any new subscribers as soon as they subscribe, while regular Subjects don't store the current value and only emit values that are published after a subscription is created.

The login() method sends the user credentials to the API via an HTTP POST request for authentication. If successful the user's basic authentication data (base64 encoded username and password) is added to the user object and stored in localStorage to keep the user logged in between page refreshes. The user object is then published to all subscribers with the call to this.currentUserSubject.next(user);.

The basic auth data is used by the basic authentication interceptor above to set the authorization header of http requests made to secure api endpoints.

The constructor() of the service initialises the currentUserSubject with the currentUser object from localStorage which enables the user to stay logged in between page refreshes or after the browser is closed. The public currentUser property is then set to this.currentUserSubject.asObservable(); which allows other components to subscribe to the currentUser Observable but doesn't allow them to publish to the currentUserSubject, this is so logging in and out of the app can only be done via the authentication service.

The currentUserValue getter allows other components an easy way to get the value of the currently logged in user without having to subscribe to the currentUser Observable.

The logout() method removes the current user object from local storage and publishes null to the currentUserSubject to notify all subscribers that the user has logged out.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { environment } from '@environments/environment';
import { User } from '@app/_models';

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
    private currentUserSubject: BehaviorSubject;
    public currentUser: Observable;

    constructor(private http: HttpClient) {
        this.currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));
        this.currentUser = this.currentUserSubject.asObservable();
    }

    public get currentUserValue(): User {
        return this.currentUserSubject.value;
    }

    login(username: string, password: string) {
        return this.http.post(`${environment.apiUrl}/users/authenticate`, { username, password })
            .pipe(map(user => {
                // store user details and basic auth credentials in local storage to keep user logged in between page refreshes
                user.authdata = window.btoa(username + ':' + password);
                localStorage.setItem('currentUser', JSON.stringify(user));
                this.currentUserSubject.next(user);
                return user;
            }));
    }

    logout() {
        // remove user from local storage to log user out
        localStorage.removeItem('currentUser');
        this.currentUserSubject.next(null);
    }
}
User Service

Path: /src/app/_services/user.service.ts

The user service contains a method for getting all users from the api, I included it to demonstrate accessing a secure api endpoint with the http authorization header set after logging in to the application, the auth header is automatically set with basic authentication credentials by the basic authentication interceptor. The secure endpoint in the example is a fake one implemented in the fake backend provider.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { environment } from '@environments/environment';
import { User } from '@app/_models';

@Injectable({ providedIn: 'root' })
export class UserService {
    constructor(private http: HttpClient) { }

    getAll() {
        return this.http.get(`${environment.apiUrl}/users`);
    }
}
Home Component Template

Path: /src/app/home/home.component.html

The home component template contains html and angular 8 template syntax for displaying a simple welcome message and a list of users from a secure api endpoint.


    #### You're logged in with Angular 8 & Basic HTTP Authentication!!

    
        ###### Users from secure api end point

        

        
            {{user.firstName}} {{user.lastName}}
        
    

Home Component

Path: /src/app/home/home.component.ts

The home component defines an angular 8 component that gets all users from the user service and makes them available to the template via a users array property.

import { Component } from '@angular/core';
import { first } from 'rxjs/operators';

import { User } from '@app/_models';
import { UserService } from '@app/_services';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent {
    loading = false;
    users: User[];

    constructor(private userService: UserService) { }

    ngOnInit() {
        this.loading = true;
        this.userService.getAll().pipe(first()).subscribe(users => {
            this.loading = false;
            this.users = users;
        });
    }
}
Login Component Template

Path: /src/app/login/login.component.html

The login component template contains a login form with username and password fields. It displays validation messages for invalid fields when the submit button is clicked. The form submit event is bound to the onSubmit() method of the login component.


    
        Username: test

        Password: test
    
    
        #### Angular 8 Basic Auth Login Example

        
            
                
                    Username
                    
                    
                        Username is required

                    
                
                
                    Password
                    
                    
                        Password is required

                    
                
                
                    
                    Login
                
                {{error}}

            
        
    

Login Component

Path: /src/app/login/login.component.ts

The login component uses the authentication service to login to the application. If the user is already logged in they are automatically redirected to the home page.

The loginForm: FormGroup object defines the form controls and validators, and is used to access data entered into the form. The FormGroup is part of the Angular Reactive Forms module and is bound to the login template above with the [formGroup]="loginForm" directive.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';

import { AuthenticationService } from '@app/_services';

@Component({ templateUrl: 'login.component.html' })
export class LoginComponent implements OnInit {
    loginForm: FormGroup;
    loading = false;
    submitted = false;
    returnUrl: string;
    error = '';

    constructor(
        private formBuilder: FormBuilder,
        private route: ActivatedRoute,
        private router: Router,
        private authenticationService: AuthenticationService
    ) { 
        // redirect to home if already logged in
        if (this.authenticationService.currentUserValue) { 
            this.router.navigate(['/']);
        }
    }

    ngOnInit() {
        this.loginForm = this.formBuilder.group({
            username: ['', Validators.required],
            password: ['', Validators.required]
        });

        // get return url from route parameters or default to '/'
        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
    }

    // convenience getter for easy access to form fields
    get f() { return this.loginForm.controls; }

    onSubmit() {
        this.submitted = true;

        // stop here if form is invalid
        if (this.loginForm.invalid) {
            return;
        }

        this.loading = true;
        this.authenticationService.login(this.f.username.value, this.f.password.value)
            .pipe(first())
            .subscribe(
                data => {
                    this.router.navigate([this.returnUrl]);
                },
                error => {
                    this.error = error;
                    this.loading = false;
                });
    }
}
App Component Template

Path: /src/app/app.component.html

The app component template is the root component template of the application, it contains the main nav bar which is only displayed for authenticated users, and a router-outlet directive for displaying the contents of each view based on the current route / path.



    
        Home
        Logout
    




    

App Component

Path: /src/app/app.component.ts

The app component is the root component of the application, it defines the root tag of the app as `` with the selector property of the @Component() decorator.

It subscribes to the currentUser observable in the authentication service so it can reactively show/hide the main navigation bar when the user logs in/out of the application. I didn't worry about unsubscribing from the observable here because it's the root component of the application, the only time the component will be destroyed is when the application is closed which would destroy any subscriptions as well.

The app component contains a logout() method which is called from the logout link in the main nav bar above to log the user out and redirect them to the login page.

import { Component } from '@angular/core';
import { Router } from '@angular/router';

import { AuthenticationService } from './_services';
import { User } from './_models';

@Component({ selector: 'app', templateUrl: 'app.component.html' })
export class AppComponent {
    currentUser: User;

    constructor(
        private router: Router,
        private authenticationService: AuthenticationService
    ) {
        this.authenticationService.currentUser.subscribe(x => this.currentUser = x);
    }

    logout() {
        this.authenticationService.logout();
        this.router.navigate(['/login']);
    }
}
App Module

Path: /src/app/app.module.ts

The app module defines the root module of the application along with metadata about the module. For more info about angular 8 modules check out this page on the official docs site.

This is where the fake backend provider is added to the application, to switch to a real backend simply remove the providers located below the comment // provider used to create fake backend.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

// used to create fake backend
import { fakeBackendProvider } from './_helpers';

import { AppComponent } from './app.component';
import { appRoutingModule } from './app.routing';

import { BasicAuthInterceptor, ErrorInterceptor } from './_helpers';
import { HomeComponent } from './home';
import { LoginComponent } from './login';

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule,
        HttpClientModule,
        appRoutingModule
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        LoginComponent
    ],
    providers: [
        { provide: HTTP_INTERCEPTORS, useClass: BasicAuthInterceptor, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },

        // provider used to create fake backend
        fakeBackendProvider
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }
App Routing Module

Path: /src/app/app.routing.ts

Routing for the Angular app is configured as an array of Routes, each component is mapped to a path so the Angular Router knows which component to display based on the URL in the browser address bar. The home route is secured by passing the AuthGuard to the canActivate property of the route.

The Routes array is passed to the RouterModule.forRoot() method which creates a routing module with all of the app routes configured, and also includes all of the Angular Router providers and directives such as the `` directive. For more information on Angular Routing and Navigation see https://angular.io/guide/router.

import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home';
import { LoginComponent } from './login';
import { AuthGuard } from './_helpers';

const routes: Routes = [
    { path: '', component: HomeComponent, canActivate: [AuthGuard] },
    { path: 'login', component: LoginComponent },

    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const appRoutingModule = RouterModule.forRoot(routes);
Production Environment Config

Path: /src/environments/environment.prod.ts

The production environment config contains variables required to run the application in production. This enables you to build the application with a different configuration for each different environment (e.g. production & development) without updating the app code.

When you build the application for production with the command ng build --prod, the output environment.ts is replaced with environment.prod.ts.

export const environment = {
    production: true,
    apiUrl: 'http://localhost:4000'
};
Development Environment Config

Path: /src/environments/environment.ts

The development environment config contains variables required to run the application in development.

Environment config is accessed by importing the environment object into any Angular service of component with the line import { environment } from '@environments/environment' and accessing properties on the environment object, see the user service for an example.

export const environment = {
    production: false,
    apiUrl: 'http://localhost:4000'
};
Main Index Html File

Path: /src/index.html

The main index.html file is the initial page loaded by the browser that kicks everything off. The Angular CLI (with Webpack under the hood) bundles all of the compiled javascript files together and injects them into the body of the index.html page so the scripts can be loaded and executed by the browser.




    
    Angular 8 - Basic HTTP Authentication Tutorial & Example
    

    
    


    Loading...


Main (Bootstrap) File

Path: /src/main.ts

The main file is the entry point used by angular to launch and bootstrap the application.

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
    enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));
Polyfills

Path: /src/polyfills.ts

Some features used by Angular 8 are not yet supported natively by all major browsers, polyfills are used to add support for features where necessary so your Angular 8 application works across all major browsers.

This file is generated by the Angular CLI when creating a new project with the ng new command, I've excluded the comments in the file for brevity.

import 'zone.js/dist/zone';
Global LESS/CSS Styles

Path: /src/styles.less

The global styles file contains LESS/CSS styles that are applied globally throughout the application.

/* You can add global styles to this file, and also import other style files */
a { cursor: pointer }
npm package.json

Path: /package.json

The package.json file contains project configuration information including package dependencies which get installed when you run npm install. Full documentation is available on the npm docs website.

{
    "name": "angular-8-basic-authentication-example",
    "version": "1.0.0",
    "scripts": {
        "ng": "ng",
        "start": "ng serve --open",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
    },
    "private": true,
    "dependencies": {
        "@angular/animations": "~8.0.1",
        "@angular/common": "~8.0.1",
        "@angular/compiler": "~8.0.1",
        "@angular/core": "~8.0.1",
        "@angular/forms": "~8.0.1",
        "@angular/platform-browser": "~8.0.1",
        "@angular/platform-browser-dynamic": "~8.0.1",
        "@angular/router": "~8.0.1",
        "rxjs": "~6.4.0",
        "tslib": "^1.9.0",
        "zone.js": "~0.9.1"
    },
    "devDependencies": {
        "@angular-devkit/build-angular": "~0.800.0",
        "@angular/cli": "~8.0.3",
        "@angular/compiler-cli": "~8.0.1",
        "@angular/language-service": "~8.0.1",
        "@types/node": "~8.9.4",
        "@types/jasmine": "~3.3.8",
        "@types/jasminewd2": "~2.0.3",
        "codelyzer": "^5.0.0",
        "jasmine-core": "~3.4.0",
        "jasmine-spec-reporter": "~4.2.1",
        "karma": "~4.1.0",
        "karma-chrome-launcher": "~2.2.0",
        "karma-coverage-istanbul-reporter": "~2.0.1",
        "karma-jasmine": "~2.0.1",
        "karma-jasmine-html-reporter": "^1.4.0",
        "protractor": "~5.4.0",
        "ts-node": "~7.0.0",
        "tslint": "~5.15.0",
        "typescript": "~3.4.3"
    }
}
TypeScript tsconfig.json

Path: /tsconfig.json

The tsconfig.json file configures how the TypeScript compiler will convert TypeScript into JavaScript that is understood by the browser. More information is available on the TypeScript docs.

Most of the file is unchanged from when it was generated by the Angular CLI, only the paths property has been added to map @app and @environments to the /src/app and /src/environments directories. This allows imports to be relative to the app and environments folders by prefixing import paths with aliases instead of having to use long relative paths (e.g. import MyComponent from '../../../MyComponent').

{
    "compileOnSave": false,
    "compilerOptions": {
        "baseUrl": "./",
        "outDir": "./dist/out-tsc",
        "sourceMap": true,
        "declaration": false,
        "downlevelIteration": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "module": "esnext",
        "moduleResolution": "node",
        "importHelpers": true,
        "target": "es2015",
        "typeRoots": [
            "node_modules/@types"
        ],
        "lib": [
            "es2018",
            "dom"
        ],
        "paths": {
            "@app/*": ["src/app/*"],
            "@environments/*": ["src/environments/*"]
        }
    }
}

The tutorial code is available on GitHub

Laravel 5.8 Tutorial - How to build user roles and permissions on Laravel 5.8 App

Laravel 5.8 Tutorial - How to build user roles and permissions on Laravel 5.8 App

In this article, you'll learn how to user build roles and permissions on Laravel 5.8 Application. You can do it acl in Laravel 5.8 using spatie composer package. I will explain how to implement User Roles and Permissions(ACL) using spatie/laravel-permission composer package.

In this article, you'll learn how to user build roles and permissions on Laravel 5.8 Application. You can do it acl in Laravel 5.8 using spatie composer package. I will explain how to implement User Roles and Permissions(ACL) using spatie/laravel-permission composer package.

Spatie role permission composer package provide way to create acl in Laravel 5.8. They provide how to assign role to user, how to assign permission to user and how to assign permission assign to roles. I will write step by step creating roles and permissions in Laravel 5.8 application.

Roles and Permissions through you can create several types of users with different role and permission, i mean some user have only see listing of items module, some user can also edit items modules, for delete and etc.

In this examples i created three modules as listed bellow:

  • User Management
  • Role Management
  • Product Management

After register user, you don't have any roles, so you can edit your details and assign admin role to you from User Management. After that you can create your own role with permission like role-list, role-create, role-edit, role-delete, product-list, product-create, product-edit, product-delete. You can check with assign new user and check that.

Step 1: Laravel 5.8 Installation

We are going from scratch so, If you haven't installed Laravel in your system then you can run bellow command and get fresh Laravel project.

composer create-project --prefer-dist laravel/laravel blog

Step 2: Install Composer Packages

Now we require to install Spatie package for ACL, that way we can use it's method. Also we will install form collection package. So Open your terminal and run bellow command.

composer require spatie/laravel-permission
  
composer require laravelcollective/html

Now open config/app.php file and add service provider and aliase.

config/app.php

'providers' => [
	....
	Spatie\Permission\PermissionServiceProvider::class,
	Collective\Html\HtmlServiceProvider::class,
],
'aliases' => [
	....
	'Form' => Collective\Html\FormFacade::class,
	'Html' => Collective\Html\HtmlFacade::class,
],

We can also custom changes on Spatie package, so if you also want to changes then you can fire bellow command and get config file in config/permission.php.

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

Step 3: Create Migrations

In this step we have to create three migrations for as listed bellow tables:

  1. users

  2. products

  3. roles

  4. permissions

  5. model_has_permissions

  6. model_has_roles

  7. role_has_permissions

So, if you install fresh project then you have already users table migration but if you don't have products table, so can create manually and other table can create using Spatie package command, so run bellow command and check migration file also.

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
php artisan make:migration create_products_table

users table:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email');
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

products table:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->text('detail');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}

Spatie tables:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePermissionTables extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $tableNames = config('permission.table_names');

        Schema::create($tableNames['permissions'], function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('guard_name');
            $table->timestamps();
        });

        Schema::create($tableNames['roles'], function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('guard_name');
            $table->timestamps();
        });

        Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames) {
            $table->integer('permission_id')->unsigned();
            $table->morphs('model');

            $table->foreign('permission_id')
                ->references('id')
                ->on($tableNames['permissions'])
                ->onDelete('cascade');

            $table->primary(['permission_id', 'model_id', 'model_type']);
        });

        Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames) {
            $table->integer('role_id')->unsigned();
            $table->morphs('model');

            $table->foreign('role_id')
                ->references('id')
                ->on($tableNames['roles'])
                ->onDelete('cascade');

            $table->primary(['role_id', 'model_id', 'model_type']);
        });

        Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) {
            $table->integer('permission_id')->unsigned();
            $table->integer('role_id')->unsigned();

            $table->foreign('permission_id')
                ->references('id')
                ->on($tableNames['permissions'])
                ->onDelete('cascade');

            $table->foreign('role_id')
                ->references('id')
                ->on($tableNames['roles'])
                ->onDelete('cascade');

            $table->primary(['permission_id', 'role_id']);

            app('cache')->forget('spatie.permission.cache');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        $tableNames = config('permission.table_names');

        Schema::drop($tableNames['role_has_permissions']);
        Schema::drop($tableNames['model_has_roles']);
        Schema::drop($tableNames['model_has_permissions']);
        Schema::drop($tableNames['roles']);
        Schema::drop($tableNames['permissions']);
    }
}

Now run migration:

php artisan migrate

Step 4: Create Models

In this step we have to create model for User and Product table, so if you get fresh project then you have User Model have so just replace code and other you should create.

app/User.php

<?php
  
namespace App;
  
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
  
class User extends Authenticatable
{
    use Notifiable;
    use HasRoles;
  
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];
  
    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
  
    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}

app/Product.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    /**
     * The attributes that are mass assignable.
     *	
     * @var array
     */
    protected $fillable = [
        'name', 'detail'
    ];
}

Step 5: Add Middleware

Spatie package provide it's in-built middleware that way we can use it simply and that is display as bellow:

role

permission

So, we have to add middleware in Kernel.php file this way :

app/Http/Kernel.php

....
protected $routeMiddleware = [
	....
	'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
	'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class,
]
....

Step 6: Create Authentication

In this step we require to create authentication of Laravel 5.8, so laravel provide artisan command to create authentication that way we don't require to create route and controller for login and registration. so run bellow command:

php artisan make:auth

Step 7: Create Routes

We require to add number of route for users module, products module and roles module. In this this route i also use middleware with permission for roles and products route, so add route this way:

routes/web.php

Auth::routes();

Route::get('/home', '[email protected]')->name('home');

Route::group(['middleware' => ['auth']], function() {
    Route::resource('roles','RoleController');
    Route::resource('users','UserController');
    Route::resource('products','ProductController');
});

Step 8: Add Controllers

In this step we have add three controller for users module, products module and roles module so you can create three controller like as bellow:

app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
use Spatie\Permission\Models\Role;
use DB;
use Hash;

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        $data = User::orderBy('id','DESC')->paginate(5);
        return view('users.index',compact('data'))
            ->with('i', ($request->input('page', 1) - 1) * 5);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $roles = Role::pluck('name','name')->all();
        return view('users.create',compact('roles'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required',
            'email' => 'required|email|unique:users,email',
            'password' => 'required|same:confirm-password',
            'roles' => 'required'
        ]);

        $input = $request->all();
        $input['password'] = Hash::make($input['password']);

        $user = User::create($input);
        $user->assignRole($request->input('roles'));

        return redirect()->route('users.index')
                        ->with('success','User created successfully');
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $user = User::find($id);
        return view('users.show',compact('user'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $user = User::find($id);
        $roles = Role::pluck('name','name')->all();
        $userRole = $user->roles->pluck('name','name')->all();

        return view('users.edit',compact('user','roles','userRole'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $this->validate($request, [
            'name' => 'required',
            'email' => 'required|email|unique:users,email,'.$id,
            'password' => 'same:confirm-password',
            'roles' => 'required'
        ]);

        $input = $request->all();
        if(!empty($input['password'])){ 
            $input['password'] = Hash::make($input['password']);
        }else{
            $input = array_except($input,array('password'));    
        }

        $user = User::find($id);
        $user->update($input);
        DB::table('model_has_roles')->where('model_id',$id)->delete();

        $user->assignRole($request->input('roles'));

        return redirect()->route('users.index')
                        ->with('success','User updated successfully');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        User::find($id)->delete();
        return redirect()->route('users.index')
                        ->with('success','User deleted successfully');
    }
}

app/Http/Controllers/ProductController.php

<?php

namespace App\Http\Controllers;

use App\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{ 
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    function __construct()
    {
         $this->middleware('permission:product-list|product-create|product-edit|product-delete', ['only' => ['index','show']]);
         $this->middleware('permission:product-create', ['only' => ['create','store']]);
         $this->middleware('permission:product-edit', ['only' => ['edit','update']]);
         $this->middleware('permission:product-delete', ['only' => ['destroy']]);
    }
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $products = Product::latest()->paginate(5);
        return view('products.index',compact('products'))
            ->with('i', (request()->input('page', 1) - 1) * 5);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('products.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        request()->validate([
            'name' => 'required',
            'detail' => 'required',
        ]);

        Product::create($request->all());

        return redirect()->route('products.index')
                        ->with('success','Product created successfully.');
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function show(Product $product)
    {
        return view('products.show',compact('product'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function edit(Product $product)
    {
        return view('products.edit',compact('product'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Product $product)
    {
         request()->validate([
            'name' => 'required',
            'detail' => 'required',
        ]);

        $product->update($request->all());

        return redirect()->route('products.index')
                        ->with('success','Product updated successfully');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function destroy(Product $product)
    {
        $product->delete();

        return redirect()->route('products.index')
                        ->with('success','Product deleted successfully');
    }
}

app/Http/Controllers/RoleController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use DB;

class RoleController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    function __construct()
    {
         $this->middleware('permission:role-list|role-create|role-edit|role-delete', ['only' => ['index','store']]);
         $this->middleware('permission:role-create', ['only' => ['create','store']]);
         $this->middleware('permission:role-edit', ['only' => ['edit','update']]);
         $this->middleware('permission:role-delete', ['only' => ['destroy']]);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        $roles = Role::orderBy('id','DESC')->paginate(5);
        return view('roles.index',compact('roles'))
            ->with('i', ($request->input('page', 1) - 1) * 5);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $permission = Permission::get();
        return view('roles.create',compact('permission'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|unique:roles,name',
            'permission' => 'required',
        ]);

        $role = Role::create(['name' => $request->input('name')]);
        $role->syncPermissions($request->input('permission'));

        return redirect()->route('roles.index')
                        ->with('success','Role created successfully');
    }
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        $role = Role::find($id);
        $rolePermissions = Permission::join("role_has_permissions","role_has_permissions.permission_id","=","permissions.id")
            ->where("role_has_permissions.role_id",$id)
            ->get();

        return view('roles.show',compact('role','rolePermissions'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        $role = Role::find($id);
        $permission = Permission::get();
        $rolePermissions = DB::table("role_has_permissions")->where("role_has_permissions.role_id",$id)
            ->pluck('role_has_permissions.permission_id','role_has_permissions.permission_id')
            ->all();

        return view('roles.edit',compact('role','permission','rolePermissions'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $this->validate($request, [
            'name' => 'required',
            'permission' => 'required',
        ]);

        $role = Role::find($id);
        $role->name = $request->input('name');
        $role->save();

        $role->syncPermissions($request->input('permission'));

        return redirect()->route('roles.index')
                        ->with('success','Role updated successfully');
    }
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        DB::table("roles")->where('id',$id)->delete();
        return redirect()->route('roles.index')
                        ->with('success','Role deleted successfully');
    }
}

Step 9: Add Blade Files

This is last step we have to add numbers view for layouts, users module, roles module, products modules and errors page, so create number of view like as bellow:

resources/views/layouts/app.blade.php

<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'Laravel 5.8 User Roles and Permissions Tutorial') }}</title>
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
    <!-- Fonts -->
    <link rel="dns-prefetch" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">
    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light navbar-laravel">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    Laravel 5.8 User Roles and Permissions - ItSolutionStuff.com
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto"></ul>

                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                            <li><a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a></li>
                            <li><a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a></li>
                        @else
                            <li><a class="nav-link" href="{{ route('users.index') }}">Manage Users</a></li>
                            <li><a class="nav-link" href="{{ route('roles.index') }}">Manage Role</a></li>
                            <li><a class="nav-link" href="{{ route('products.index') }}">Manage Product</a></li>
                            <li class="nav-item dropdown">
                                <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                                    <a class="dropdown-item" href="{{ route('logout') }}"
                                       onclick="event.preventDefault();
                                                     document.getElementById('logout-form').submit();">
                                        {{ __('Logout') }}
                                    </a>

                                    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                        @csrf
                                    </form>
                                </div>
                            </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>

        <main class="py-4">
            <div class="container">
            @yield('content')
            </div>
        </main>
    </div>
</body>
</html>

resources/views/users/index.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2>Users Management</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-success" href="{{ route('users.create') }}"> Create New User</a>
        </div>
    </div>
</div>

@if ($message = Session::get('success'))
<div class="alert alert-success">
  <p>{{ $message }}</p>
</div>
@endif

<table class="table table-bordered">
 <tr>
   <th>No</th>
   <th>Name</th>
   <th>Email</th>
   <th>Roles</th>
   <th width="280px">Action</th>
 </tr>
 @foreach ($data as $key => $user)
  <tr>
    <td>{{ ++$i }}</td>
    <td>{{ $user->name }}</td>
    <td>{{ $user->email }}</td>
    <td>
      @if(!empty($user->getRoleNames()))
        @foreach($user->getRoleNames() as $v)
           <label class="badge badge-success">{{ $v }}</label>
        @endforeach
      @endif
    </td>
    <td>
       <a class="btn btn-info" href="{{ route('users.show',$user->id) }}">Show</a>
       <a class="btn btn-primary" href="{{ route('users.edit',$user->id) }}">Edit</a>
        {!! Form::open(['method' => 'DELETE','route' => ['users.destroy', $user->id],'style'=>'display:inline']) !!}
            {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
        {!! Form::close() !!}
    </td>
  </tr>
 @endforeach
</table>

{!! $data->render() !!}

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/users/create.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2>Create New User</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="{{ route('users.index') }}"> Back</a>
        </div>
    </div>
</div>

@if (count($errors) > 0)
  <div class="alert alert-danger">
    <strong>Whoops!</strong> There were some problems with your input.<br><br>
    <ul>
       @foreach ($errors->all() as $error)
         <li>{{ $error }}</li>
       @endforeach
    </ul>
  </div>
@endif


{!! Form::open(array('route' => 'users.store','method'=>'POST')) !!}
<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {!! Form::text('name', null, array('placeholder' => 'Name','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Email:</strong>
            {!! Form::text('email', null, array('placeholder' => 'Email','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Password:</strong>
            {!! Form::password('password', array('placeholder' => 'Password','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Confirm Password:</strong>
            {!! Form::password('confirm-password', array('placeholder' => 'Confirm Password','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Role:</strong>
            {!! Form::select('roles[]', $roles,[], array('class' => 'form-control','multiple')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 text-center">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</div>
{!! Form::close() !!}

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/users/edit.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2>Edit New User</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="{{ route('users.index') }}"> Back</a>
        </div>
    </div>
</div>

@if (count($errors) > 0)
  <div class="alert alert-danger">
    <strong>Whoops!</strong> There were some problems with your input.<br><br>
    <ul>
       @foreach ($errors->all() as $error)
         <li>{{ $error }}</li>
       @endforeach
    </ul>
  </div>
@endif

{!! Form::model($user, ['method' => 'PATCH','route' => ['users.update', $user->id]]) !!}
<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {!! Form::text('name', null, array('placeholder' => 'Name','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Email:</strong>
            {!! Form::text('email', null, array('placeholder' => 'Email','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Password:</strong>
            {!! Form::password('password', array('placeholder' => 'Password','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Confirm Password:</strong>
            {!! Form::password('confirm-password', array('placeholder' => 'Confirm Password','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Role:</strong>
            {!! Form::select('roles[]', $roles,$userRole, array('class' => 'form-control','multiple')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 text-center">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</div>
{!! Form::close() !!}

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/users/show.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2> Show User</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="{{ route('users.index') }}"> Back</a>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {{ $user->name }}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Email:</strong>
            {{ $user->email }}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Roles:</strong>
            @if(!empty($user->getRoleNames()))
                @foreach($user->getRoleNames() as $v)
                    <label class="badge badge-success">{{ $v }}</label>
                @endforeach
            @endif
        </div>
    </div>
</div>
@endsection

resources/views/roles/index.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2>Role Management</h2>
        </div>
        <div class="pull-right">
        @can('role-create')
            <a class="btn btn-success" href="{{ route('roles.create') }}"> Create New Role</a>
            @endcan
        </div>
    </div>
</div>

@if ($message = Session::get('success'))
    <div class="alert alert-success">
        <p>{{ $message }}</p>
    </div>
@endif

<table class="table table-bordered">
  <tr>
     <th>No</th>
     <th>Name</th>
     <th width="280px">Action</th>
  </tr>
    @foreach ($roles as $key => $role)
    <tr>
        <td>{{ ++$i }}</td>
        <td>{{ $role->name }}</td>
        <td>
            <a class="btn btn-info" href="{{ route('roles.show',$role->id) }}">Show</a>
            @can('role-edit')
                <a class="btn btn-primary" href="{{ route('roles.edit',$role->id) }}">Edit</a>
            @endcan
            @can('role-delete')
                {!! Form::open(['method' => 'DELETE','route' => ['roles.destroy', $role->id],'style'=>'display:inline']) !!}
                    {!! Form::submit('Delete', ['class' => 'btn btn-danger']) !!}
                {!! Form::close() !!}
            @endcan
        </td>
    </tr>
    @endforeach
</table>

{!! $roles->render() !!}

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/roles/create.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2>Create New Role</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="{{ route('roles.index') }}"> Back</a>
        </div>
    </div>
</div>

@if (count($errors) > 0)
    <div class="alert alert-danger">
        <strong>Whoops!</strong> There were some problems with your input.<br><br>
        <ul>
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
        </ul>
    </div>
@endif

{!! Form::open(array('route' => 'roles.store','method'=>'POST')) !!}
<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {!! Form::text('name', null, array('placeholder' => 'Name','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Permission:</strong>
            <br/>
            @foreach($permission as $value)
                <label>{{ Form::checkbox('permission[]', $value->id, false, array('class' => 'name')) }}
                {{ $value->name }}</label>
            <br/>
            @endforeach
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 text-center">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</div>
{!! Form::close() !!}

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/roles/edit.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2>Edit Role</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="{{ route('roles.index') }}"> Back</a>
        </div>
    </div>
</div>

@if (count($errors) > 0)
    <div class="alert alert-danger">
        <strong>Whoops!</strong> There were some problems with your input.<br><br>
        <ul>
        @foreach ($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
        </ul>
    </div>
@endif

{!! Form::model($role, ['method' => 'PATCH','route' => ['roles.update', $role->id]]) !!}
<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {!! Form::text('name', null, array('placeholder' => 'Name','class' => 'form-control')) !!}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Permission:</strong>
            <br/>
            @foreach($permission as $value)
                <label>{{ Form::checkbox('permission[]', $value->id, in_array($value->id, $rolePermissions) ? true : false, array('class' => 'name')) }}
                {{ $value->name }}</label>
            <br/>
            @endforeach
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12 text-center">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</div>
{!! Form::close() !!}

@endsection
<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>

resources/views/roles/show.blade.php

@extends('layouts.app')

@section('content')
<div class="row">
    <div class="col-lg-12 margin-tb">
        <div class="pull-left">
            <h2> Show Role</h2>
        </div>
        <div class="pull-right">
            <a class="btn btn-primary" href="{{ route('roles.index') }}"> Back</a>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Name:</strong>
            {{ $role->name }}
        </div>
    </div>
    <div class="col-xs-12 col-sm-12 col-md-12">
        <div class="form-group">
            <strong>Permissions:</strong>
            @if(!empty($rolePermissions))
                @foreach($rolePermissions as $v)
                    <label class="label label-success">{{ $v->name }},</label>
                @endforeach
            @endif
        </div>
    </div>
</div>
@endsection

resources/views/products/index.blade.php

@extends('layouts.app')

@section('content')
    <div class="row">
        <div class="col-lg-12 margin-tb">
            <div class="pull-left">
                <h2>Products</h2>
            </div>
            <div class="pull-right">
                @can('product-create')
                <a class="btn btn-success" href="{{ route('products.create') }}"> Create New Product</a>
                @endcan
            </div>
        </div>
    </div>

    @if ($message = Session::get('success'))
        <div class="alert alert-success">
            <p>{{ $message }}</p>
        </div>
    @endif

    <table class="table table-bordered">
        <tr>
            <th>No</th>
            <th>Name</th>
            <th>Details</th>
            <th width="280px">Action</th>
        </tr>
	    @foreach ($products as $product)
	    <tr>
	        <td>{{ ++$i }}</td>
	        <td>{{ $product->name }}</td>
	        <td>{{ $product->detail }}</td>
	        <td>
                <form action="{{ route('products.destroy',$product->id) }}" method="POST">
                    <a class="btn btn-info" href="{{ route('products.show',$product->id) }}">Show</a>
                    @can('product-edit')
                    <a class="btn btn-primary" href="{{ route('products.edit',$product->id) }}">Edit</a>
                    @endcan

                    @csrf
                    @method('DELETE')
                    @can('product-delete')
                    <button type="submit" class="btn btn-danger">Delete</button>
                    @endcan
                </form>
	        </td>
	    </tr>
	    @endforeach
    </table>

    {!! $products->links() !!}

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/products/create.blade.php

@extends('layouts.app')

@section('content')
    <div class="row">
        <div class="col-lg-12 margin-tb">
            <div class="pull-left">
                <h2>Add New Product</h2>
            </div>
            <div class="pull-right">
                <a class="btn btn-primary" href="{{ route('products.index') }}"> Back</a>
            </div>
        </div>
    </div>

    @if ($errors->any())
        <div class="alert alert-danger">
            <strong>Whoops!</strong> There were some problems with your input.<br><br>
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif

    <form action="{{ route('products.store') }}" method="POST">
    	@csrf

         <div class="row">
		    <div class="col-xs-12 col-sm-12 col-md-12">
		        <div class="form-group">
		            <strong>Name:</strong>
		            <input type="text" name="name" class="form-control" placeholder="Name">
		        </div>
		    </div>
		    <div class="col-xs-12 col-sm-12 col-md-12">
		        <div class="form-group">
		            <strong>Detail:</strong>
		            <textarea class="form-control" style="height:150px" name="detail" placeholder="Detail"></textarea>
		        </div>
		    </div>
		    <div class="col-xs-12 col-sm-12 col-md-12 text-center">
		            <button type="submit" class="btn btn-primary">Submit</button>
		    </div>
		</div>

    </form>

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/products/edit.blade.php

@extends('layouts.app')

@section('content')
    <div class="row">
        <div class="col-lg-12 margin-tb">
            <div class="pull-left">
                <h2>Edit Product</h2>
            </div>
            <div class="pull-right">
                <a class="btn btn-primary" href="{{ route('products.index') }}"> Back</a>
            </div>
        </div>
    </div>

    @if ($errors->any())
        <div class="alert alert-danger">
            <strong>Whoops!</strong> There were some problems with your input.<br><br>
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif

    <form action="{{ route('products.update',$product->id) }}" method="POST">
    	@csrf
        @method('PUT')

         <div class="row">
		    <div class="col-xs-12 col-sm-12 col-md-12">
		        <div class="form-group">
		            <strong>Name:</strong>
		            <input type="text" name="name" value="{{ $product->name }}" class="form-control" placeholder="Name">
		        </div>
		    </div>
		    <div class="col-xs-12 col-sm-12 col-md-12">
		        <div class="form-group">
		            <strong>Detail:</strong>
		            <textarea class="form-control" style="height:150px" name="detail" placeholder="Detail">{{ $product->detail }}</textarea>
		        </div>
		    </div>
		    <div class="col-xs-12 col-sm-12 col-md-12 text-center">
		      <button type="submit" class="btn btn-primary">Submit</button>
		    </div>
		</div>

    </form>

<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>
@endsection

resources/views/products/show.blade.php

@extends('layouts.app')

@section('content')
    <div class="row">
        <div class="col-lg-12 margin-tb">
            <div class="pull-left">
                <h2> Show Product</h2>
            </div>
            <div class="pull-right">
                <a class="btn btn-primary" href="{{ route('products.index') }}"> Back</a>
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12">
            <div class="form-group">
                <strong>Name:</strong>
                {{ $product->name }}
            </div>
        </div>
        <div class="col-xs-12 col-sm-12 col-md-12">
            <div class="form-group">
                <strong>Details:</strong>
                {{ $product->detail }}
            </div>
        </div>
    </div>
@endsection
<p class="text-center text-primary"><small>Tutorial by ItSolutionStuff.com</small></p>

Step 10: Handle Exertion Error

Now, in this step we will handle exertion. if you don't have a permission and try to access that page using browser url then you can give message as like bellow:

add/Exceptions/Handler.php

......
public function render($request, Exception $exception)
{
    if ($exception instanceof \Spatie\Permission\Exceptions\UnauthorizedException) {
        return response()->json(['User have not permission for this page access.']);
    }
 
    return parent::render($request, $exception);
}
....

Step 11: Create Seeder For Permissions and AdminUser

In this step we will create seeder for permissions, Right now we have fixed permission so we create using seeder as listed bellow, but if you can add more permission as you want:

1.role-list

2.role-create

3.role-edit

4.role-delete

5.product-list

6.product-create

7.product-edit

8.product-delete

So, first create seeder using bellow command:

php artisan make:seeder PermissionTableSeeder

And put bellow code in PermissionTableSeeder seeder this way:

database/seeds/PermissionTableSeeder.php

<?php

use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;

class PermissionTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
       $permissions = [
           'role-list',
           'role-create',
           'role-edit',
           'role-delete',
           'product-list',
           'product-create',
           'product-edit',
           'product-delete'
        ];

        foreach ($permissions as $permission) {
             Permission::create(['name' => $permission]);
        }
    }
}

After this we have to run bellow command for run PermissionTableSeeder seeder:

php artisan db:seed --class=PermissionTableSeeder

Now let's create new seeder for creating admin user.

php artisan make:seeder CreateAdminUserSeeder

database/seeds/PermissionTableSeeder.php

<?php
  
use Illuminate\Database\Seeder;
use App\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
  
class CreateAdminUserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $user = User::create([
        	'name' => 'Hardik Savani', 
        	'email' => '[email protected]',
        	'password' => bcrypt('123456')
        ]);
  
        $role = Role::create(['name' => 'Admin']);
   
        $permissions = Permission::pluck('id','id')->all();
  
        $role->syncPermissions($permissions);
   
        $user->assignRole([$role->id]);
    }
}
php artisan db:seed --class=CreateAdminUserSeeder

Now we are ready to to run full example of ACL. so let's run our example so run bellow command for quick run:

php artisan serve

Access By

http://localhost:8000/

Now you can login with following credential:

Email: [email protected]
Password: 123456

You can see bellow screenshots:

You can download code from GitHub

Creating a Modal Dialog in Angular 8 with TypeScript

Creating a Modal Dialog in Angular 8 with TypeScript

In this tutorial we'll cover how to implement modal windows (dialog boxes) in Angular 8 with TypeScript. The example is a custom modal without the need for any 3rd party libraries.

There are plenty of plugins and libraries out there that include modal windows, in the past I used them myself when I needed to add a modal to a new project. The main issue I have with 3rd party plugins is that they usually contain a lot of features I don't need which adds unnecessary bloat to my Angular app, so a while ago I took some time to implement a custom modal window to see how difficult it would be and also to remove the magic & mystery I had in my mind about exactly how modals work.

When I finished I was pleasantly surprised at the relatively small amount of code required to implement a custom modal window, most of the modal 'magic' is done with a handful of CSS styles (see modal.component.less) while Angular / TypeScript is just used for showing and hiding the modal windows.

Running the Angular 8 Modal Dialog Locally
  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download or clone the project source code from https://github.com/cornflourblue/angular-8-custom-modal
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder.

NOTE: You can also run the app directly using the Angular CLI command ng serve --open. To do this first install the Angular CLI globally on your system with the command npm install -g @angular/cli.

Adding Custom Modals to Your Angular 8 App

To add modals to your Angular 8 application you'll need to copy the /src/app/_modal folder and contents from the example project, the folder contains the modal module and associated files, including:

  • modal.model.less - LESS/CSS styles for displaying modal dialogs, this is where the modal "magic" happens.
  • modal.component.html - modal component template that contains the wrapper html for displaying modal dialogs.
  • modal.component.ts - modal component with the logic for displaying modal dialogs.
  • modal.module.ts - modal module that encapsulates the modal component so it can be imported by the app module.
  • modal.service.ts - modal service that can be used by any angular component to open and close modal dialogs.
  • index.ts - barrel file that re-exports the modal module and service so they can be imported using only the folder path instead of the full path to each file, and also enables importing from multiple files with a single import.

Import the Modal Module into your App Module

To make the modal component available to your Angular 8 application you need to add the ModalModule to the imports array of your App Module (app.module.ts). See the app module from the example app below, the modal module is imported on line 5 and added to the imports array of the app module on line 16.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { ModalModule } from './_modal';
import { appRoutingModule } from './app.routing';

import { AppComponent } from './app.component';
import { HomeComponent } from './home';
import { TestPageComponent } from './test-page';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        ModalModule,
        appRoutingModule
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        TestPageComponent
    ],
    bootstrap: [AppComponent]
})

export class AppModule { }

Add the tag to pages where you want to display modals

To add a modal dialog to any page simply add the <jw-modal id="[insert unique id]"></jw-modal> tag along with the content for the modal. You can put any content you like inside the <jw-modal> element. You can also update the modal LESS/CSS if you want to change the styles of the modals, e.g to make them smaller or add CSS animation transitions.

IMPORTANT: A unique id is required for each modal on a page, it can be any string e.g. 'custom-modal-1'. The id string is used by the modal service to keep track of each active modal in the angular app, so the service knows which modal to open/close based on the id passed to the modalService.open() and modalService.close() methods e.g. modalService.open('custom-modal-1').

Here is the home component template from the example app (/src/app/home/home.component.html) that contains two modals, each is opened by a button click, and the first modal contains an input text field that allows you to edit the bodyText displayed in the template.

<div>
    <h1>Home</h1>
    <p>{{bodyText}}</p>
    <button (click)="openModal('custom-modal-1')">Open Modal 1</button>
    <button (click)="openModal('custom-modal-2')">Open Modal 2</button>
</div>

<jw-modal id="custom-modal-1">
    <h1>A Custom Modal!</h1>
    <p>Home page text: <input type="text" [(ngModel)]="bodyText" /></p>
    <button (click)="closeModal('custom-modal-1');">Close</button>
</jw-modal>

<jw-modal id="custom-modal-2">
    <h1 style="height:1000px">A Tall Custom Modal!</h1>
    <button (click)="closeModal('custom-modal-2');">Close</button>
</jw-modal>	

Opening & Closing Angular 8 Modal Dialogs

To open a modal call the modalService.open() method with the id of the modal you want to open, e.g. modalService.open('custom-modal-1'). To close a modal call the modalService.close() method with the id of the modal you want to close, e.g. modalService.close('custom-modal-1').

By default modals are closed on background click, to disable this remove the chunk of code in the modal component (/src/app/_modal/modal.component.ts) located directly below the comment // close modal on background click.

Here is the home component from the example app (/src/app/home/home.component.ts), it contains methods for opening and closing modals (openModal() and closeModal()) that call the corresponding methods of the modal service.

import { Component, OnInit } from '@angular/core';

import { ModalService } from '../_modal';

@Component({ templateUrl: 'home.component.html' })
export class HomeComponent implements OnInit {
    bodyText: string;

    constructor(private modalService: ModalService) { }

    ngOnInit() {
        this.bodyText = 'This text can be updated in modal 1';
    }

    openModal(id: string) {
        this.modalService.open(id);
    }

    closeModal(id: string) {
        this.modalService.close(id);
    }
}	
Breakdown of the Angular 8 Custom Modal Code

Below is a breakdown of the pieces of code used to implement custom modal dialogs in Angular 8 & TypeScript, you don't need to know the details of how it all works to use the modals in your project, it's only if you're interested in the nuts and bolts or if you want to modify the underlying code or behaviour.

LESS/CSS Styles for Angular 8 Modal Dialogs

These are the styles applied to the custom modal dialogs in this example, they could also be used in non-angular projects as it's just pure LESS/CSS.

I prefixed the modal element and classes with jw- to prevent conflicts with 3rd party css libraries such as Bootstrap.

/* MODAL STYLES
-------------------------------*/
jw-modal {
    /* modals are hidden by default */
    display: none;

    .jw-modal {
        /* modal container fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;

        /* z-index must be higher than .jw-modal-background */
        z-index: 1000;
        
        /* enables scrolling for tall modals */
        overflow: auto;

        .jw-modal-body {
            padding: 20px;
            background: #fff;

            /* margin exposes part of the modal background */
            margin: 40px;
        }
    }

    .jw-modal-background {
        /* modal background fixed across whole screen */
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;

        /* semi-transparent black  */
        background-color: #000;
        opacity: 0.75;
        
        /* z-index must be below .jw-modal and above everything else  */
        z-index: 900;
    }
}

body.jw-modal-open {
    /* body overflow is hidden to hide main scrollbar when modal window is open */
    overflow: hidden;
}	
Angular 8 Modal Service

The Angular 8 modal service manages the communication that's required between page components and modal components. It maintains a list of available modals on the page and exposes methods for interacting with those modals.

import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ModalService {
    private modals: any[] = [];

    add(modal: any) {
        // add modal to array of active modals
        this.modals.push(modal);
    }

    remove(id: string) {
        // remove modal from array of active modals
        this.modals = this.modals.filter(x => x.id !== id);
    }

    open(id: string) {
        // open modal specified by id
        const modal = this.modals.find(x => x.id === id);
        modal.open();
    }

    close(id: string) {
        // close modal specified by id
        const modal = this.modals.find(x => x.id === id);
        modal.close();
    }
}	
Angular 8 Modal Component

The custom modal component is used to add modal windows anywhere in your angular application by using the <jw-modal> tag. Each modal instance adds itself to the modal service when it loads by calling modalService.add(this) from the ngOnInit Angular lifecycle method, and removes itself from the modal service when it is destroyed by calling modalService.remove(this.id) from the ngOnDestroy Angular lifecycle method.

import { Component, ViewEncapsulation, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';

import { ModalService } from './modal.service';

@Component({ 
    selector: 'jw-modal', 
    templateUrl: 'modal.component.html', 
    styleUrls: ['modal.component.less'],
    encapsulation: ViewEncapsulation.None
})
export class ModalComponent implements OnInit, OnDestroy {
    @Input() id: string;
    private element: any;

    constructor(private modalService: ModalService, private el: ElementRef) {
        this.element = el.nativeElement;
    }

    ngOnInit(): void {
        // ensure id attribute exists
        if (!this.id) {
            console.error('modal must have an id');
            return;
        }

        // move element to bottom of page (just before </body>) so it can be displayed above everything else
        document.body.appendChild(this.element);

        // close modal on background click
        this.element.addEventListener('click', el => {
            if (el.target.className === 'jw-modal') {
                this.close();
            }
        });

        // add self (this modal instance) to the modal service so it's accessible from controllers
        this.modalService.add(this);
    }

    // remove self from modal service when component is destroyed
    ngOnDestroy(): void {
        this.modalService.remove(this.id);
        this.element.remove();
    }

    // open modal
    open(): void {
        this.element.style.display = 'block';
        document.body.classList.add('jw-modal-open');
    }

    // close modal
    close(): void {
        this.element.style.display = 'none';
        document.body.classList.remove('jw-modal-open');
    }
}	
Angular 8 Modal Component Template

The modal component template contains just a couple of wrapper divs for the modal content and a div for the modal background. The <ng-content> element is replaced by Angular with the contents you set inside the <jw-modal> element, this is called Angular content projection.

<div class="jw-modal">
    <div class="jw-modal-body">
        <ng-content></ng-content>
    </div>
</div>
<div class="jw-modal-background"></div>	

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading about AngularJS

Angular 8 (formerly Angular 2) - The Complete Guide

Angular & NodeJS - The MEAN Stack Guide

The Complete Node.js Developer Course (3rd Edition)

The Web Developer Bootcamp

Best 50 Angular Interview Questions for Frontend Developers in 2019

MEAN Stack Angular 8 CRUD Web Application

Angular 8 Tutorial - User Registration and Login Example

How to build a CRUD Web App with Angular 8.0

Building CRUD Mobile App using Ionic 4, Angular 8

Angular 8 Material Design Tutorial & Example