Login and Sign up Page Lesson
Sign up/Login Wireframes
What the common HTML elements needed
- Container: The Div that holds all the elements inside of it, first layer
- Common SASS that a Container will have:
- display
- justify-content/align items
- color
- Card: A Div that will hold username, password buttons
- inputs and buttons: Where the user will put there information/login
Needed HTML properties
- Most HTML Elements:
- class: used to assign html elements to there own SASS
- id: used mostly in javascript to identify a single element in an entire page full of HTML
- inputs and buttons:
- Placeholder: puts text inside an input to help the user what to input
- Buttons:
- onclick: runs a javascript function when clicked
- note: onclick can be replaced with event listeners, although onclick is simpler to code
Bare Code
1
2
3
4
5
6
7
8
| <div class="CONTAINER">
<div class="CARD">
<h3>Login</h3>
<input id="signInEmailInput" class="input" placeholder="Email">
<input id="signInPasswordInput" class="input" placeholder="Password">
<button class="signInButton" onclick="login_user()">Login</button>
</div>
</div>
|
SASS
- Be SASSy when it come to designing your wireframes
- Nobody wants to look at some boring designs when signing up or logging in
- Make sure to add some SASS to your code
Not SASSy code
SASSy Code
Copy this code into your custom-styles.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // Cool color palette
$color1: #5D737E; $color2: #64B6AC; $color3: #D0F4DE; $color4: #1B4332;
// Warm color palette
$color5: #FFB447; $color6: #FF3E4D; $color7: #FF1E56; $color8: #FFBD69;
// Animating Backgrounds
@keyframes fade1 {
0% { background-color: $color1} 25% { background-color: $color2} 50% { background-color: $color3} 75% { background-color: $color4} 100% { background-color: $color1}
}
@keyframes fade2 {
0% { background-color: $color5} 25% { background-color: $color6} 50% { background-color: $color7} 75% { background-color: $color8} 100% { background-color: $color5}
}
.login-container {
display: flex;
justify-content: center;
align-items: center;
color: black;
.card {
width: 300px;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
animation: fade1 5s ease infinite;
}
.card2 {
width: 300px;
padding: 20px;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
animation: fade2 5s ease infinite;
}
h3 {
margin-bottom: 20px;
}
.input {
width: 100%;
padding: 10px;
margin-bottom: 10px;
}
}
|
Popcorn Hacks
- Change the colors in the keyframe animation
- Extra points if you can make a completely different keyframe that looks cool
overview of userDbRequest function (part 1 of the login/signup puzzle)
what does this even do?
- primary function is to add data to our frontend html fragment, which will store our data.
- this would be a creating data in our frontend database view which users can see with authentication aka login. (part 2)
this html fragment represents our table which we fill with data using our function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| function userDbRequest() {
// prepare HTML result container for new output
const resultContainer = document.getElementById("result");
// set options for cross origin header request
const options = {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'include', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
},
};
fetch("http://localhost:8085/api/person/", options)
.then(response => {
if (response.status !== 200) {
const errorMsg = 'Database response error: ' + response.status;
console.log(errorMsg);
const tr = document.createElement("tr");
const td = document.createElement("td");
td.innerHTML = errorMsg;
tr.appendChild(td);
resultContainer.appendChild(tr);
return;
}
// valid response will contain json data
response.json().then(data => {
console.log(data);
for (const row of data) {
// tr and td build out for each row
const tr = document.createElement("tr");
const name = document.createElement("td");
const id = document.createElement("td");
const age = document.createElement("td");
// data is specific to the API
name.innerHTML = row.name;
id.innerHTML = row.email;
age.innerHTML = row.age;
// this build td's into tr
tr.appendChild(name);
tr.appendChild(id);
tr.appendChild(age);
// add HTML to container
resultContainer.appendChild(tr);
}
})
})
// catch fetch errors (ie ACCESS to server blocked)
.catch(err => {
console.error(err);
const tr = document.createElement("tr");
const td = document.createElement("td");
td.innerHTML = err + ": " + url;
tr.appendChild(td);
resultContainer.appendChild(tr);
});
}
|
popcorn hack
- make sure you understand how you can adapt this to your project. What role with userDbRequest() play in your project? How will the row and column values be changed for your specific purposes?
LOG IN
overview of user authentication aka how to do login (part 2 of the login/signup puzzle)
what do we need to do for login?
- create function that sends authentication request to backend
- create backend methods to only allow authenticated users to send GET request to backend when accessing the database on frontend
- implement those backend methods into frontend
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| function login_user() {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
// STEP ONE: COLLECT USER INPUT
var raw = JSON.stringify({
"email": document.getElementById("signInEmailInput").value,
"password": document.getElementById("signInPasswordInput").value
// For quick testing
//"email": "toby@gmail.com",
//"password": "123Toby!"
});
console.log(raw);
var requestOptions = {
method: 'POST',
headers: myHeaders,
credentials: 'include',
body: raw,
redirect: 'follow'
};
// STEP TWO: SEND REQUEST TO BACKEND AND GET JWT COOKIE
fetch("http://localhost:8085/authenticate", requestOptions)
.then(response => {
if (!response.ok) {
const errorMsg = 'Login error: ' + response.status;
console.log(errorMsg);
switch (response.status) {
case 401:
alert("Incorrect username or password");
break;
case 403:
alert("Access forbidden. You do not have permission to access this resource.");
break;
case 404:
alert("User not found. Please check your credentials.");
break;
// Add more cases for other status codes as needed
default:
alert("Login failed. Please try again later.");
}
return Promise.reject('Login failed');
}
return response.text()
})
.then(result => {
console.log(result);
window.location.href = "http://127.0.0.1:4100/Login-Lesson/account";
})
.catch(error => console.error('Error during login:', error));
}
|
what does it do?
this function sends an authentication request to backend, which then redirects to our database page.
the authentication request in question is located in spring_portfolio/mvc/jwt/JwtApiController.java
FOR REFERENCE:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| @PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody Person authenticationRequest) throws Exception {
authenticate(authenticationRequest.getEmail(), authenticationRequest.getPassword());
final UserDetails userDetails = personDetailsService
.loadUserByUsername(authenticationRequest.getEmail());
final String token = jwtTokenUtil.generateToken(userDetails);
final ResponseCookie tokenCookie = ResponseCookie.from("jwt", token)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.sameSite("None; Secure")
// .domain("example.com") // Set to backend domain
.build();
return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, tokenCookie.toString()).build();
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
} catch (Exception e) {
throw new Exception(e);
}
}
|
this is the authentication method we are calling to, which provides us with the cookie we need for access to our database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
function fetchUserData() {
var requestOptions = {
method: 'GET',
mode: 'cors',
cache: 'default',
credentials: 'include',
};
fetch("http://localhost:8085/api/person/jwt", requestOptions)
.then(response => {
if (!response.ok) {
const errorMsg = 'Login error: ' + response.status;
console.log(errorMsg);
switch (response.status) {
case 401:
alert("Please log into or make an account");
window.location.href = "http://127.0.0.1:4100/Login-Lesson/loginSignup";
break;
case 403:
alert("Access forbidden. You do not have permission to access this resource.");
break;
case 404:
alert("User not found. Please check your credentials.");
break;
// Add more cases for other status codes as needed
default:
alert("Login failed. Please try again later.");
}
return Promise.reject('Login failed');
}
return response.json();
// Success!!!
})
.then(data => {
// Display user data above the table
const userDataContainer = document.getElementById("userData");
userDataContainer.innerHTML = `
<img src="/Login-Lesson/images/defaultUser.png" width="250" height="250">
<h1><strong>${data.name}</strong></h1>
<p>Email: ${data.email}</p>
<p>Age: ${data.age}</p>
<p>ID: ${data.id}</p>
<button onclick="signOut()">Sign Out</button>
`;
console.log(data);
})
.catch(error => console.log('error', error));
}
|
what does it do?
this function resides in our database display on our frontend, and does 2 things
- access our jwt method and check for authentication, then get the database if so
- handle errors for when authentication fails
backend method
1
2
3
4
5
6
7
| @GetMapping("/jwt")
@PreAuthorize("isAuthenticated()") // Restrict access to authenticated users
public ResponseEntity<Person> getAuthenticatedPersonData() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
Person person = repository.findByEmail(username); // Retrieve data for the authenticated user
return new ResponseEntity<>(person, HttpStatus.OK);
}
|
so how do I add login?
- create a login function on the frontend which can send authentication request to backend. make sure it passes user input as variables for the authentication request
- in database page, create method which calls a get method that checks for authentication first before displaying the data.
- add error handling for when our authentication fails
SIGN UP
Steps
- Collect user input
- Put user input into a url as parameters
- Send POST to backend to make user
- (RECOMMENDED) Redirect/Refresh page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| function signup_user() {
var requestOptions = {
method: 'POST',
mode: 'cors',
cache: 'no-cache'
};
// Collect user input
let fetchName = document.getElementById("signUpNameInput").value;
let fetchEmail = document.getElementById("signUpEmailInput").value;
let fetchPassword = document.getElementById("signUpPasswordInput").value;
let fetchDob = document.getElementById("signUpDobInput").value;
// Posting in backend only works if user input is sent as query parameters
let requestURL = `http://localhost:8085/api/person/post?email=${fetchEmail}&password=${fetchPassword}&name=${fetchName}&dob=${fetchDob}`;
console.log(requestURL)
fetch(requestURL, requestOptions)
.then(response => {
if (!response.ok) {
return response.text().then(errorMsg => {
alert('Error: ' + errorMsg);
});
}
// Success!!!
alert("Signup Complete");
// Redirect to Database location
location.reload();
})
.catch(error => {
alert('An unexpected error occurred: ' + error.message);
});
}
|
Notable Backend Code
Security
1
2
3
4
5
6
7
8
9
10
| .authorizeHttpRequests(auth -> auth
.requestMatchers("/authenticate").permitAll()
.requestMatchers("/mvc/person/update/**", "/mvc/person/delete/**").authenticated()
// .requestMatchers("/api/person/post/**", "/api/person/delete/**").authenticated()
// Removed so anyone without a cookie can post
.requestMatchers("/api/person/delete/**").authenticated()
.requestMatchers("/**").permitAll()
)
|
Endpoint Mapping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| @PostMapping( "/post")
// @RequestParam is why user input needs to be a parameter
public ResponseEntity<Object> postPerson(@RequestParam("email") String email,
@RequestParam("password") String password,
@RequestParam("name") String name,
@RequestParam("dob") String dobString) {
Date dob;
// dob handling
try {
dob = new SimpleDateFormat("MM-dd-yyyy").parse(dobString);
} catch (Exception e) {
return new ResponseEntity<>(dobString +" error; try MM-dd-yyyy", HttpStatus.BAD_REQUEST);
}
// A person object WITHOUT ID will create a new record with default roles as student
Person person = new Person(email, password, name, dob);
personDetailsService.save(person);
return new ResponseEntity<>(email +" is created successfully", HttpStatus.CREATED);
}
|
PULL REQUESTS
- If anyone can make the signout button clear the jwt cookie and redirect to login page (I was not able to figure this out)
- Make your own endpoint mapping in the backend and have it do something you think is useful/cool