The story so far
Groovy! We have all of the CRUD. We are the CRUDiest. CRUDtastic.
One thing left: security. More specifically, we'll talk about authorization, or user permissions. This is one part of security, but an important part.
Earlier, you learned how to add Edit and Delete buttons to every record. Trouble is, that lets anyone delete your data. They go to the right URL, and keep clicking the Delete button.
Remember, normally DBs are kept on servers, so if one persons deletes data, it's gone for everyone.
Let's add some authorization.
Flag variable
Let's keep it simple. We'll have a variable called loggedIn
. It will have "yes" in it, if a user with admin permissions is logged in.
We need four things:
- Login form: set
loggedIn
to "yes" if the user gives the right username and password. - A menu to show the log in and out links.
- Logout: erase
loggedIn
. - On every admin page: read
loggedIn
. If it isn't "yes", leave the page.
Different pages will be using the variable loggedIn
. The login form will use it, the logout mechanism (however we do that), and every admin page. Admin pages include at least:
- The admin record list page.
- The add/edit page.
- The database initialization page.
We need a way to share a variable across pages. We know how to use localStorage
, so let's use that again.
Let's make just the authorization part of an app about sloths.
Our app will track sloths in a sloth sanctuary, also known as a slotel. You can try the app.
Files
Here are our files:
The library
files are as usual. So is the main index.html
. It's the public list file.
admin
has one new file: login.html
. That's the login from.
Let's start with that.
Login form
The form looks like this:
Here's the HTML:
Everything should be familiar, except that line 14 uses the password
field type:
<input type="password"...
Here's the login button's click code (line 18):
onclick="Slotel.login()"
What should that do? Here's some pseudocode:
If the username and password are valid:
Set the logged in flag.
Jump to the main admin page.
Else:
Show an error message.
Here's the code:
Line 2 gets the username the user typed:
var userName = $("#user-name").val().trim().toLocaleLowerCase();
It strips off leading and trailing spaces, and converts the username to lowercase. This means that usernames are not case-sensitive. The user could type:
- BobbyJoe
- Bobbyjoe
- BOBBYJOE
- bobbyjoe
For all of them, the variable userName
would have the value bobbyjoe
.
Line 3 gets the password:
var userPassword = $("#user-password").val().trim();
It trims spaces, but doesn't convert to lowercase. Passwords are case-sensitive:
- If the user typed the password "BatmandBegins", the variable
userPassword
is set toBatmandBegins
. - If the user typed the password "batmandbegins", the variable
userPassword
is set tobatmandbegins
.
OK, so now, userName
has whatever the user typed in the username field, and userPassword
has whatever the user typed in the password field. Trimmed and (maybe) lower-cased, ready for processing.
The next line of code is:
if ( userName == 'buffy' && userPassword == 'giles' ) {
There is only one username/password pair in this app.
What happens if that's true? Here's the pseudocode again:
If the username and password are valid:
Set the logged in flag.
Jump to the main admin page.
Else:
Show an error message.
Here's the code:
Line 8 is the important one:
localStorage.setItem("slotelLoggedIn", "yes");
It stores "yes" into localStorage
, under the key slotelLoggedIn
. It's called slotelLoggedIn
rather than loggedIn
, for namespacing. It's possible that more than one app wants to use the key loggedIn
in the same localStorage
. Unlikely, given the way we organize things. Still, adding the app name slotel
to the front of the key means that we never have to think about it.
Here's the pseudocode again:
If the username and password are valid:
Set the logged in flag.
Jump to the main admin page.
Else:
Show an error message. UP TO HERE
Here's the JS for the last line:
alert("Sorry, username and/or password are invalid.");
That's the login form done. You can try it, and grab the code.
This is terrible security, of course. People can see the username and password by pressing Ctrl+U, and looking at the page's source code. In a real app, the username and password would be checked on a server. The valid usernames and passwords would be in a database on the server. Still, the principle is the same as we have here. A login form. The user types in a username and password. If it's valid, set a flag, and jump to the main admin page.
Let's go back to our to-do list:
- Login form: set
loggedIn
to "yes" if the user gives the right username and password. - A menu to show the log in and out links.
- Logout: erase
loggedIn
. - On every admin page: read
loggedIn
. If it isn't "yes", leave the page.
We've done the first one. The second we'll do with the navbar.
Navbar menu
Let's make the navbar help the user log in and out. The menu will look like this when the user has not logged in:
(Let's push the menu to the right of the navbar, just to try something different.)
So, when the user is logged out, the menu shows them a Login link.
What is the user is logged in? Here's the menu:
The menu is part of the navbar. The code for the navbar is in library/includes/navbar.html
. Recall the our standard template loads the navbar into every page:
$("#navbar").load("library/includes/navbar.html");
Here's some HTML from navbar.html
. There are three links:
All of them are hidden to begin with:
The page load code shows some of the links, depending on whether the user is logged in.
$(document).ready(function () {
if ( localStorage.getItem('slotelLoggedIn') == "yes") {
$("#admin-link").show();
$("#logout-link").show();
}
else {
$("#login-link").show();
}
});
/crud/sloths/index.html
.
Is that because they have to work on every page?
navbar.html
is loaded into every page in the app. These pages:
The menu links have to work for all of the files, no matter where they are in the directory tree. That's why relative links won't do the job. Root relative links work, because they always start at the root of the site (which in this case is https://webappexamples.cybercour.se
):
/crud/sloths/index.html
Logging out
We've done the first two things on the to-do list:
- Login form: set
loggedIn
to "yes" if the user gives the right username and password. - A menu to show the log in and out links.
- Logout: erase
loggedIn
. UP TO HERE - On every admin page: read
loggedIn
. If it isn't "yes", leave the page.
Here's the HTML for the logout link:
<li class="nav-item hide-on-load" id="logout-link">
<a class="nav-link"
href="#" onclick="Slotel.logout()">Log out</a>
</li>
The link runs the function Slotel.logout()
. Notice the href
is just #
. That means it doesn't actually go anywhere.
Here's Slotel.logout()
:
Slotel.logout = function(){
//Erase the logged in flag.
localStorage.setItem("slotelLoggedIn", "no");
//Jump to home page.
window.location.href = "/crud/sloths/index.html";
};
The comments explain what happens.
Checking the login flag
Here's our to-do list:
- Login form: set
loggedIn
to "yes" if the user gives the right username and password. - A menu to show the log in and out links.
- Logout: erase
loggedIn
. - On every admin page: read
loggedIn
. If it isn't "yes", leave the page. UP TO HERE
Only the last one is left.
We want to add code to every admin page. These ones:
These pages should only be shown to logged in users.
This pseudocode will work:
If not logged in:
Jump to log in page
This is convenient for users. If they are not logged in, take them to the page where they can log in.
Here's the JS, added to the page load event:
if ( localStorage.getItem('slotelLoggedIn') != "yes" ) {
window.location.href = "login.html";
}
That's it!
Summary
- We added a variable to
localStorage
to be a flag, to show whether the user is logged in. - There's a page with a login form, that sets the flag to "yes" if the user gives the right username and password.
- The navbar has a menu that's loaded on every page. All of the links are hidden at first. Code in the page load event shows some of them. Which ones are shown depends on whether the user is logged in or not
- Every admin page checks to see whether the user is logged in. If not, the browser jumps to the log in page.