Let’s add a beautiful login form and user profile in the previous MEAN Stack Project, Inside that we already implemented JWT Authentication in Back-end Node JS API.
GitHub link up-to this article : https://goo.gl/GVBRPo.
So far we have discussed following related topics in MEAN Stack:
- CRUD Operation – Insert Update View and Delete.
- User Registration
Node JS Back End.
Angular 6 Front End. - JWT User Authentication
Node JS Back End.
Angular 6 Front End( this one).
Initial Works
First of all, I’ll update default style sheet – styles.css as follows.
/* You can add global styles to this file, and also import other style files */
@import url('https://fonts.googleapis.com/css?family=Poppins');
/* BASIC */
html {
background-color: #56baed;
}
body {
font-family: "Poppins", sans-serif;
height: 100vh;
}
a {
color: #92badd;
display:inline-block;
text-decoration: none;
font-weight: 400;
}
h2 {
text-align: center;
font-size: 16px;
font-weight: 600;
text-transform: uppercase;
display:inline-block;
margin: 40px 8px 10px 8px;
color: #cccccc;
}
/* STRUCTURE */
.wrapper {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
width: 100%;
padding: 20px;
}
#formContent {
-webkit-border-radius: 10px 10px 10px 10px;
border-radius: 10px 10px 10px 10px;
background: #fff;
padding: 30px;
width: 90%;
max-width: 450px;
position: relative;
padding: 0px;
-webkit-box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3);
box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3);
text-align: center;
}
#formFooter {
background-color: #f6f6f6;
border-top: 1px solid #dce8f1;
padding: 25px;
text-align: center;
-webkit-border-radius: 0 0 10px 10px;
border-radius: 0 0 10px 10px;
}
/* TABS */
h2.inactive {
color: #cccccc;
}
h2.active {
color: #0d0d0d;
border-bottom: 2px solid #5fbae9;
}
/* FORM TYPOGRAPHY*/
input[type=button], input[type=submit], input[type=reset] {
cursor: pointer;
background-color: #56baed;
border: none;
color: white;
padding: 15px 80px;
text-align: center;
text-decoration: none;
display: inline-block;
text-transform: uppercase;
font-size: 13px;
-webkit-box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4);
box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4);
-webkit-border-radius: 5px 5px 5px 5px;
border-radius: 5px 5px 5px 5px;
margin: 5px 20px 40px 20px;
-webkit-transition: all 0.3s ease-in-out;
-moz-transition: all 0.3s ease-in-out;
-ms-transition: all 0.3s ease-in-out;
-o-transition: all 0.3s ease-in-out;
transition: all 0.3s ease-in-out;
}
input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
background-color: #39ace7;
}
input[type=button]:active, input[type=submit]:active, input[type=reset]:active {
-moz-transform: scale(0.95);
-webkit-transform: scale(0.95);
-o-transform: scale(0.95);
-ms-transform: scale(0.95);
transform: scale(0.95);
}
input[type=submit]:disabled{
background-color: grey;
color: white;
}
input[type=text],input[type=password] {
background-color: #f6f6f6;
border: none;
color: #0d0d0d;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 5px;
width: 85%;
border: 2px solid #f6f6f6;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
-webkit-border-radius: 5px 5px 5px 5px;
border-radius: 5px 5px 5px 5px;
}
input[type=text]:focus,input[type=password]:focus {
background-color: #fff;
border-bottom: 2px solid #5fbae9;
}
input[type=text]:placeholder {
color: #cccccc;
}
input[type=text].invalid-textbox,input[type=password].invalid-textbox{
border-bottom: 2px solid #ed5558;
}
label.validation-message{
color:#ed5558;
}
/* ANIMATIONS */
/* Simple CSS3 Fade-in-down Animation */
.fadeInDown {
-webkit-animation-name: fadeInDown;
animation-name: fadeInDown;
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
@-webkit-keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
100% {
opacity: 1;
-webkit-transform: none;
transform: none;
}
}
@keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
100% {
opacity: 1;
-webkit-transform: none;
transform: none;
}
}
/* Simple CSS3 Fade-in Animation */
@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
.fadeIn {
opacity:0;
-webkit-animation:fadeIn ease-in 1;
-moz-animation:fadeIn ease-in 1;
animation:fadeIn ease-in 1;
-webkit-animation-fill-mode:forwards;
-moz-animation-fill-mode:forwards;
animation-fill-mode:forwards;
-webkit-animation-duration:1s;
-moz-animation-duration:1s;
animation-duration:1s;
}
.fadeIn.first {
-webkit-animation-delay: 0.4s;
-moz-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.fadeIn.second {
-webkit-animation-delay: 0.6s;
-moz-animation-delay: 0.6s;
animation-delay: 0.6s;
}
.fadeIn.third {
-webkit-animation-delay: 0.8s;
-moz-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.fadeIn.fourth {
-webkit-animation-delay: 1s;
-moz-animation-delay: 1s;
animation-delay: 1s;
}
/* Simple CSS3 Fade-in Animation */
.underlineHover:after {
display: block;
left: 0;
bottom: -10px;
width: 0;
height: 2px;
background-color: #56baed;
content: "";
transition: width 0.2s;
}
.underlineHover:hover {
color: #0d0d0d;
}
.underlineHover:hover:after{
width: 100%;
}
/* OTHERS */
*:focus {
outline: none;
}
#icon {
width:50%;
}
* {
box-sizing: border-box;
}
.alert {
padding: 20px;
background-color: #f44336; /* Red */
color: white;
margin-bottom: 15px;
}
.success{
padding: 20px;
background-color:#249424; /* Green */
color: white;
margin-bottom: 15px;
}
/* Table Styles */
.table-fill {
background: white;
border-radius:3px;
border-collapse: collapse;
height: 320px;
margin: auto;
max-width: 600px;
padding:5px;
width: 100%;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
animation: float 5s infinite;
}
th {
color:#D5DDE5;;
background:#1b1e24;
border-bottom:4px solid #9ea7af;
border-right: 1px solid #343a45;
font-size:23px;
font-weight: 100;
padding:24px;
text-align:left;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
vertical-align:middle;
}
th:first-child {
border-top-left-radius:3px;
}
th:last-child {
border-top-right-radius:3px;
border-right:none;
}
tr {
border-top: 1px solid #C1C3D1;
border-bottom: 1px solid #C1C3D1;
color:#666B85;
font-size:16px;
font-weight:normal;
text-shadow: 0 1px 1px rgba(256, 256, 256, 0.1);
}
tr:first-child {
border-top:none;
}
tr:last-child {
border-bottom:none;
}
tr:nth-child(odd) td {
background:#EBEBEB;
}
tr:last-child td:first-child {
border-bottom-left-radius:3px;
}
tr:last-child td:last-child {
border-bottom-right-radius:3px;
}
td {
background:#FFFFFF;
padding:20px;
text-align:left;
vertical-align:middle;
font-weight:300;
font-size:18px;
text-shadow: -1px -1px 1px rgba(0, 0, 0, 0.1);
border-right: 1px solid #C1C3D1;
}
td:last-child {
border-right: 0px;
}
th.text-left {
text-align: left;
}
th.text-center {
text-align: center;
}
th.text-right {
text-align: right;
}
td.text-left {
text-align: left;
}
td.text-center {
text-align: center;
}
td.text-right {
text-align: right;
}
One more thing, I want to add a top image for previous user registration form. so first add an image user.png inside assets/img folder. then add an additional div for this image in sign-up.component.html as follows.
Now user registration form will look like this.
Application Structure
Here is the final application structure.
● src
+---● app
| +--● user
| | |--user.component.ts|.html|.css
| | +--● sign-up
| | | |--sign-up.component.ts|.html|.css
| | +--● sign-in
| | | |--sign-in.component.ts|.html|.css
| +--● user-profile
| | |--user-profile.component.ts|.html|.css
| |
| |--● shared
| | |--user.service.ts
| | |--user.model.ts
| |
| |--app.module.ts
| |--routes.ts
|
+---● auth
|
+-- auth.gaurd.ts
+-- auth.interceptor.ts
as per the structure we have to create two components, one gaurd file and one interceptor.for that you can use following commands.
// from app folder
ng g c userProfile
// from user folder
ng g c signUp
// from new folder - auth
ng g g auth
we’ll create the interceptor file later.
Configure Routing
let’s update existing routes file for signIn and userProfile components.
that’s what we needed for this application.
Now let’s design two tabs in user.component.html – one for login form and one for user registration form. so let’s update user.component.html.
routerLink set href attribute for anchor element. routerLinkActive apply css class ‘active’ when current url is same as anchor href.
Design Login Form
update signIn.component.ts with following properties, we need them for designing login form.
Now let’s update corresponding html file.
top div image is there in img folder. simple form validation is implemented as we have seen in previous user registration tutorial. error div will display error message stored inside the property (serverErrorMessages), after form submit event.
Now our login form look like this.
Now let’s implement the submit event by defining the function – onSubmit inside component Typescript file.
UserService class function login is to be defined, inside that we make post request to ‘/authenticate’ from Node JS API by passing email and password.
Inside the success callback function, we save jwt inside local storage with key – ‘token’ ( setToken function is to be defined in service class) and the userProfile component will be shown. Error callback will store the error message in serverErrorMessage property.
Now let’s update UserService class with new functions.
along with setToken function we have functions for reading and deleting jwt token from Local Storage.
Inside the signIn component, we have created an object of UserService class, as part of service class injection in app.module.ts file. so you have to remove the service class from signUp component providers array( added during user registration form design). Now add them in app.model.ts file as follows.
Secure Private Routes
After successful authentication, userProfile component will be shown, But I can directly access the same userProfile without authentication by editing URL. So let’s block such type of unauthorized request. for that we use guards from Angular, we have created a guard – auth.guard.ts. so we’ll update the file as follows.
Inside this guard function – canActivate, we just need to define the criteria to check whether a user is logged in or not. For that we have called isLoggedIn fuction ( which is to be defined). If not logged in, we re-direct the user to login page, delete his token and return false. Otherwise it return true. Before using the guard, we have to define isLoggedIn function in service class as follows.
getUserPayload function will return information stored in jwt payload, if there is no token it returns false. Inside isLoggedIn function we call getUserPayload, if there is token it will check for token expiration using exp claim.
Now we need to use AuthGuard class in routes.ts file. Before that we have add guard class in app.module.ts file.
Now protect userprofile route in routes.ts using this AuthGuard class. so update userprofile route in routes.ts file as follows.
Implement User Profile
Inside user profile component I want to show the details of logged in user. so first of all define getUserProfile function in UserService class as follows.
We implemented this ‘/userprofile’ as a private route in Node JS, so we have to send jwt token in request header. Before that let’s design the component. so we can update user-profile.component.ts as follows.
Inside ngOnInit lifecycle hook, user details send from the API is stored in userDetails property. You can predict what is happening inside onLogout function. So let’s use these in component html file.
With these, userProfile component will look like this.
Add JWT to Request Header Using HttpInterceptor
Now let’s add JWT into request header, for that we use HttpInterceptor. so first of all create the auth.interceptor.ts as follows.
Here we have this AuthInterceptor class which implements HttpInterceptor Interface. So we must define the function intercept. From now onwards all request from the application will go through this function. Hence all common function can be done inside the function, Here we are going to add JWT token into request header for request which doesn’t have noauth in their header. noauth property will be set to true for request which doesn’t demand authorization. such request will be send to API without making any change. so that’s why we called next function directly.
Else we set JWT in Authorization header and then we make the request. Inside tap-error callback function, we will redirect the user to login page.
Add this new interceptor into app.module.ts.
Now we have to update all API call with noauth property in service class, which does not need authentication.
Finally if you want to redirect the user to user profile when authorized user access login page. You can update the sign in component ngOnInit lifecycle hook as follows.