Building a login system with HTML, CSS, and JavaScript

Zell Liew 🤗 - Feb 13 '20 - - Dev Community

I built a login system with HTML, CSS, and JavaScript when I made Learn JavaScript's student portal. I'd like to share this system with you since it seems to work well.

How the system works

The system goes like this:

  1. Let user login at the login page
    1. Upon login, store user's information in localStorage.
    2. Redirect to the content page
  2. When student lands on a page
    1. Check if student can access the page
    2. If yes, allow student to enter
    3. If no, redirect to login page

Logging in

Students can log in to the course with their email address and a password.

Login form. It has two fields: email address and password.

When they submit the form, I send their email and password to my server through a POST request. Here's what the request looks like:

async function basiclogin(email, password) {
  const response = await zlFetch.post(loginEndpoint, {
    auth: {
      username: email,
      password: password
    },
    body: {
      /*...*/
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

:::note
zlFetch is a library I built to make the Fetch API easier to use. You can find out more about zlFetch here. The auth option transforms username and password into a basic authentication header.
:::

My server uses JSON Web Tokens (JWT) to authenticate users. It sends back a JWT token. The JWT token is a long string that looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmaXJzdE5hbWUiOiJaZWxsIiwiZW1haWwiOiJ6ZWxsd2tAZ21haWwuY29tIiwiaWF0IjoxNTc3ODYzNzc3LCJleHAiOjE1ODA0NTU3Nzd9.i3rOwqV1Bc-JEAaxT7lHZHUlDuFf9ADVP7qjy50WqT0
Enter fullscreen mode Exit fullscreen mode

This token acts as credentials for future logins. I save this token inside localStorage so I can log students in automatically.

async function basiclogin(email, password) {
  const response = await zlFetch.post(/*...*/);
  const { token } = response.body;

  localStorage.setItem("token", token);
}
Enter fullscreen mode Exit fullscreen mode

Checking if the student is logged in

To check whether a student is logged in, I check the localStorage for the token. If localStorage doesn't have a token, I know the student has not logged in.

async function isLoggedIn() {
  const token = store.get("token");
  if (!token) return false;
}
Enter fullscreen mode Exit fullscreen mode

If the student is not logged in, I redirect them to the login page.

async function autoRedirect() {
  const validLogin = await isLoggedIn();
  if (!validLogin && location.pathname !== "/login/") redirect("/login");
  if (validLogin && location.pathname === "/login/") redirect("/");
}
Enter fullscreen mode Exit fullscreen mode

If the localStorage has a token, I still need to check the validity of this token. To do this, I send another POST request to my server.

async function isLoggedIn() {
  // ...
  // Checks validity of token
  const response = await zlFetch.post(loginEndpoint, {
    auth: token,
    body: { course: "learn-javascript" }
  });
}
Enter fullscreen mode Exit fullscreen mode

If the response is successful, my server returns another token with a new expiry date. This new token allows students to remain logged in for a longer period.

async function isLoggedIn() {
  // ...
  // Saves token into localStorage again
  const { token } = response.body;
  localStorage.setItem("token", token);

  return true;
}
Enter fullscreen mode Exit fullscreen mode

Updating a student's access level

Besides token, I store a student's "access level" inside localStorage as well. This "access level" determines what lessons a student can access.


Students can access these lessons


Students cannot access these lessons

I store this access level when the student logs in for the first time.

function basiclogin (email, password) {
  const response = await zlFetch.post(/*...*/)
  const { token, user } = response.body
  // ...

  // user contains accessLevel
  localStorage.setItem('user', user)
}
Enter fullscreen mode Exit fullscreen mode

I store the access level again when the token is validated. This allows me to:

  1. Prevent students from tampering with their localStorage (and getting access to lessons they should not have)
  2. Update a student's access automatically once they upgraded to a higher tier

Two birds with one stone!

function isLoggedIn() {
  // ...
  const { token, user } = response.body;
  localStorage.setItem("user", user);
}
Enter fullscreen mode Exit fullscreen mode

Logging out

It's simple to logout. We just have to clear the items we placed in localStorage.

function logout() {
  localStorage.removeItem("token");
  localStorage.removeItem("user");
}
Enter fullscreen mode Exit fullscreen mode

Preventing access for students without JavaScript

This course is built with a static site generator. Each lesson is a plain HTML file. Students can bypass the authentication layer and read the HTML directly if they turned off JavaScript.

This should not happen.

To prevent people from turning off their JavaScript to view lessons, I added a no-js class to the HTML element.

<html lang="en" class="no-js">
  ...
</html>
Enter fullscreen mode Exit fullscreen mode

I remove this no-js class when there's JavaScript.

document.documentElement.classList.remove("no-js");
Enter fullscreen mode Exit fullscreen mode

And I hide the main content if the user turned off JavaScript.

/* Disallow access if there's no JavaScript */
.no-js main {
  display: none !important;
}
Enter fullscreen mode Exit fullscreen mode

A message to turn on JavaScript

Students who try to access the course portal without JavaScript will see a blank page. They may get confused and think the page didn't load.

I need to tell these students to turn on JavaScript, so I added a <noscript> tag.

<noscript
  >This course portal requires JavaScript to verify your identity. Please enable
  JavaScript to access the course.</noscript
>
Enter fullscreen mode Exit fullscreen mode

Noscript message

That's the entire login process!


Thanks for reading. This article was originally posted on my blog. Sign up for my newsletter if you want more articles to help you become a better frontend developer.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .