Foundation project

Project: a secure sign-up form

Lesson 12 of 20 · 22 min · Module 3: Passwords and sessions

Put the first three modules together in a sign-up form, the kind of form many real sites get wrong. It checks what people type, enforces sensible password rules, sends the data carefully, and shows the server’s answers without trusting them.

Start this lesson →

Free. No installation: you write and run the code in your browser, and it is checked automatically.

By the end, you’ll be able to…

  • Combine validation, password rules and safe output in one form
  • Send credentials carefully and handle every answer
  • Keep passwords out of logs and storage

The idea

Check the username with an allow-list and the password with the rules you know, show the first problem next to its field, and move focus there. When everything is fine, send JSON with fetch, show the server’s answer with textContent, and never log or store the password. The form’s autocomplete values already help password managers.

const problem = usernameProblem('Ana Smith!');
if (problem) {
  document.querySelector('#username-error').textContent = problem;
  document.querySelector('#username').focus();
}

01Check early, check again later

Checks in the browser give instant feedback and save a request, but the server must repeat every one of them, because requests can be sent without your form.

02Answers from servers are data too

Messages in a server’s answer can contain text that came from people, such as a username someone chose. Show them with textContent, like any other untrusted text.

03Help password managers

autocomplete="username" and autocomplete="new-password" tell password managers what each field is for, so they can offer a strong generated password and fill it in later. Never block pasting into password fields.

<input type="password" autocomplete="new-password">

Your project brief

Build the form:
• usernameProblem(username) returns Use 3 to 20 lowercase letters, digits or underscores. or ''.
• passwordProblem(password, username) returns the first problem, or '': Use at least 15 characters., Use 64 characters or fewer., This password is too common. (in COMMON, ignoring capitals), Do not include your username. (usernames of 3 or more characters, ignoring capitals). Count characters with [...password].length.
• On submit: prevent the default, show both messages in #username-error and #password-error, and focus the first invalid field. Otherwise disable #create and POST { username, email, password } as JSON to /api/signup. On success show Account created. Welcome, ana_92! (with the real username) and reset the form; otherwise show the answer’s error; if the request fails, show Could not reach the server. Please try again. Enable the button again at the end.
• Never write the password to the console or to storage.

What the checks look for

  • Invalid usernames stop the form
  • Weak passwords stop the form
  • The password cannot contain the username
  • A valid form is sent as JSON
  • Success is announced and the form cleared
  • Server errors are shown as plain text
  • The button waits while the request is sent
  • The password stays out of logs and storage
  • A lost connection is explained
Try it in the editor →

Take this with you

Validate, send carefully, show answers as text, and keep passwords to yourself.

Go a little further

Before sending the form, check the password against the leaked-password list from the challenge “Has this password leaked?”.

Learn Web Security Foundations for free

20 short lessons with real code and instant checks.

See the whole course →