Token-Based Authentication With Angular and Node



Authentication is one of the most important parts of any web application. This tutorial discusses token-based authentication systems and how they differ from traditional login systems. At the end of this tutorial, you'll see a fully working demo written in Angular and Node.js.
Traditional Authentication Systems
Before proceeding with a token-based authentication system, let's have a look at a traditional authentication system first.
- The user provides a username and password in the login form and clicks Log In.
- After the request is made, validate the user on the back-end by querying the database. If the request is valid, create a session by using the user information fetched from the database, and then return the session information in the response header in order to store the session ID in the browser.
- Provide the session information for accessing restricted endpoints in the application.
- If the session information is valid, let the user access the specified endpoints, and respond with the rendered HTML content.



Everything is fine up to this point. The web application works well, and it is able to authenticate users so that they may access restricted endpoints. However, what happens when you want to develop another client, say for Android, for your application? Will you be able to use the current application to authenticate mobile clients and serve restricted content? As it currently stands, no. There are two main reasons for this:
- Sessions and cookies do not make sense for mobile applications. You cannot share sessions or cookies created on the server-side with mobile clients.
- In the current application, the rendered HTML is returned. In a mobile client, you need something like JSON or XML to be included as the response.
In this case, you need a client-independent application.
Token-Based Authentication
In token-based authentication, cookies and sessions will not be used. A token will be used for authenticating a user for each request to the server. Let's redesign the first scenario with token-based authentication.
It will use the following flow of control:
- The user provides a username and password in the login form and clicks Log In.
- After a request is made, validate the user on the back end by querying in the database. If the request is valid, create a token by using the user information fetched from the database, and then return that information in the response header so that we can store the token browser in local storage.
- Provide token information in every request header for accessing restricted endpoints in the application.
- If the token fetched from the request header information is valid, let the user access the specified endpoint, and respond with JSON or XML.
In this case, we have no returned session or cookie, and we have not returned any HTML content. That means that we can use this architecture for any client for a specific application. You can see the architecture schema below:



So what is this JWT?
JWT
JWT stands for JSON Web Token and is a token format used in authorization headers. This token helps you to design communication between two systems in a secure way. Let's rephrase JWT as the "bearer token" for the purposes of this tutorial. A bearer token consists of three parts: header, payload, and signature.
- The header is the part of the token that keeps the token type and encryption method, which is also encrypted with base-64.
- The payload includes the information. You can put in any kind of data like user info, product info, and so on, all of which is stored with base-64 encryption.
- The signature consists of combinations of the header, payload, and secret key. The secret key must be kept securely on the server-side.
You can see the JWT schema and an example token below:



You do not need to implement the bearer token generator as you can find established packages in several languages. You can see some of them below:
A Practical Example
After covering some basic information about token-based authentication, we can now proceed with a practical example. Take a look at the following schema, after which we'll analyze it in more detail:



- The requests are made by several clients, such as a web application or a mobile client, to the API for a specific purpose.
- The requests are made to a service like
https://api.yourexampleapp.com
. If lots of people use the application, multiple servers may be required to serve the requested operation. - Here, the load balancer is used for balancing requests to best suit the application servers at the back-end. When you make a request to
https://api.yourexampleapp.com
, first the load balancer will handle a request, and then it will redirect the client to a specific server. - There is one application, and this application is deployed to several servers (server-1, server-2, ..., server-n). Whenever a request is made to
https://api.yourexampleapp.com
, the back-end application will intercept the request header and extract token information from the authorization header. A database query will be made by using this token. If this token is valid and has the required permission to access the requested endpoint, it will continue. If not, it will return a 403 response code (which indicates a forbidden status).
Advantages
Token-based authentication comes with several advantages that solve serious problems. Here are a few of them:
Client-Independent Services
In token-based authentication, a token is transferred via request headers, instead of keeping the authentication information in sessions or cookies. This means there is no state. You can send a request to the server from any type of client that can make HTTP requests.
Content Delivery Networks (CDNs)
In most current web applications, views are rendered on the back-end, and HTML content is returned to the browser. Front-end logic depends on back-end code.
There is no need to make such a dependency. This comes with several problems. For example, if you are working with a design agency that implements your front-end HTML, CSS, and JavaScript, you need to take that front-end code and migrate it into your back-end code in order to do some rendering or populating operations. After some time, your rendered HTML content will differ greatly from what the code agency implemented.
In token-based authentication, you can develop a front-end project separately from the back-end code. Your back-end code will return a JSON response instead of rendered HTML, and you can put the minified, gzipped version of the front-end code into the CDN. When you go to your web page, the HTML content will be served from the CDN, and the page content will be populated by API services using the token in the authorization headers.
No Cookie-Session (or No CSRF)
CSRF is a major problem in modern web security because it doesn't check whether a request source is trusted or not. To solve this problem, a token pool is used for sending that token on every form post. In token-based authentication, a token is used in authorization headers, and CSRF does not include that information.
Persistent Token Store
When a session read, write, or delete operation is made in the application, it will make a file operation in the operating system's temp
folder, at least for the first time. Let's say that you have multiple servers, and a session is created on the first server. When you make another request and your request drops in another server, session information will not exist and will get an "unauthorized" response. I know, you can solve that with a sticky session. However, in token-based authentication, this case is solved naturally. There is no sticky session problem because the request token is intercepted on every request on any server.
Those are the most common advantages of token-based authentication and communication. That's the end of the theoretical and architectural talk about token-based authentication. Time for a practical example.
An Example Application
You will see two applications to demonstrate token-based authentication:
- token-based-auth-backend
- token-based-auth-frontend
In the back-end project, there will be service implementations, and service results will be in JSON format. There is no view returned in services. In the front-end project, there will be an Angular project for front-end HTML and then the front-end app will be populated by Angular services to make requests to the back-end services.
token-based-auth-backend
In the back-end project, there are three main files:
- package.json is for dependency management.
- models/User.js contains a User model that will be used for making database operations about users.
- server.js is for project bootstrapping and request handling.
That's it! This project is very simple, so that you can understand the main concept easily without doing a deep dive.
1 |
{ |
2 |
"name": "angular-restful-auth", |
3 |
"version": "0.0.1", |
4 |
"dependencies": { |
5 |
"body-parser": "^1.20.2", |
6 |
"express": "4.x", |
7 |
"express-jwt": "8.4.1", |
8 |
"jsonwebtoken": "9.0.0", |
9 |
"mongoose": "7.3.1", |
10 |
"morgan": "latest" |
11 |
}, |
12 |
"engines": { |
13 |
"node": ">=0.10.0" |
14 |
} |
15 |
} |
package.json contains dependencies for the project: express
for MVC, body-parser
for simulating post request handling in Node.js, morgan
for request logging, mongoose
for our ORM framework to connect to MongoDB, and jsonwebtoken
for creating JWT tokens by using our User model. There is also an attribute called engines
that says that this project is made using Node.js version >= 0.10.0. This is useful for PaaS services like Heroku. We will also cover that topic in another section.
1 |
const mongoose = require('mongoose'); |
2 |
const Schema = mongoose.Schema; |
3 |
|
4 |
const UserSchema = new Schema({ |
5 |
email: String, |
6 |
password: String, |
7 |
token: String |
8 |
});
|
9 |
|
10 |
module.exports = mongoose.model('User', UserSchema); |
We said that we would generate a token by using the user model payload. This model helps us to make user operations on MongoDB. In User.js, the user-schema is defined and the User model is created by using a mongoose model. This model is ready for database operations.
Our dependencies are defined, and our user model is defined, so now let's combine all those to construct a service for handling specific requests.
1 |
// Required Modules
|
2 |
const express = require("express"); |
3 |
const morgan = require("morgan"); |
4 |
const bodyParser = require("body-parser"); |
5 |
const jwt = require("jsonwebtoken"); |
6 |
const mongoose = require("mongoose"); |
7 |
const app = express(); |
In Node.js, you can include a module in your project by using require
. First, we need to import the necessary modules into the project:
1 |
const port = process.env.PORT || 3001; |
2 |
const User = require('./models/User'); |
3 |
|
4 |
// Connect to DB |
5 |
mongoose.connect(process.env.MONGO_URL); |
Our service will serve through a specific port. If any port variable is defined in the system environment variables, you can use that, or we have defined port 3001
. After that, the User model is included, and the database connection is established in order to do some user operations. Do not forget to define an environment variable—MONGO_URL
—for the database connection URL.
1 |
app.use(bodyParser.urlencoded({ extended: true })); |
2 |
app.use(bodyParser.json()); |
3 |
app.use(morgan("dev")); |
4 |
app.use(function(req, res, next) { |
5 |
res.setHeader('Access-Control-Allow-Origin', '*'); |
6 |
res.setHeader('Access-Control-Allow-Methods', 'GET, POST'); |
7 |
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization'); |
8 |
next(); |
9 |
});
|
10 |
In the above section, we've made some configurations for simulating an HTTP request handling in Node by using Express. We are allowing requests to come from different domains in order to develop a client-independent system. If you do not allow this, you will trigger a CORS (Cross Origin Request Sharing) error in the web browser.
-
Access-Control-Allow-Origin
allowed for all domains. - You can send
POST
andGET
requests to this service. -
X-Requested-With
andcontent-type
headers are allowed.
1 |
app.post('/authenticate', async function(req, res) { |
2 |
try { |
3 |
const user = await User.findOne({ email: req.body.email, password: req.body.password }).exec(); |
4 |
if (user) { |
5 |
res.json({ |
6 |
type: true, |
7 |
data: user, |
8 |
token: user.token |
9 |
});
|
10 |
} else { |
11 |
res.json({ |
12 |
type: false, |
13 |
data: "Incorrect email/password" |
14 |
});
|
15 |
}
|
16 |
} catch (err) { |
17 |
res.json({ |
18 |
type: false, |
19 |
data: "Error occurred: " + err |
20 |
});
|
21 |
}
|
22 |
});
|
We have imported all of the required modules and defined our configuration, so now it's time to define request handlers. In the above code, whenever you make a POST
request to /authenticate
with username and password, you will get a JWT
token. First, the database query is processed by using a username and password. If a user exists, the user data will be returned with its token. But what if there is no such user matching the username and/or password?
1 |
app.post('/signin', async function(req, res) { |
2 |
try { |
3 |
const existingUser = await User.findOne({ email: req.body.email }).exec(); |
4 |
if (existingUser) { |
5 |
res.json({ |
6 |
type: false, |
7 |
data: "User already exists!" |
8 |
});
|
9 |
} else { |
10 |
const userModel = new User(); |
11 |
userModel.email = req.body.email; |
12 |
userModel.password = req.body.password; |
13 |
const savedUser = await userModel.save(); |
14 |
savedUser.token = jwt.sign(savedUser.toObject(), process.env.JWT_SECRET); |
15 |
const updatedUser = await savedUser.save(); |
16 |
res.json({ |
17 |
type: true, |
18 |
data: updatedUser, |
19 |
token: updatedUser.token |
20 |
});
|
21 |
}
|
22 |
} catch (err) { |
23 |
res.json({ |
24 |
type: false, |
25 |
data: "Error occurred: " + err |
26 |
});
|
27 |
}
|
28 |
});
|
When you make a POST
request to /signin
with username and password, a new user will be created by using posted user information. On the 14th
line, you can see that a new JSON token is generated by using the jsonwebtoken
module, which has been assigned to the jwt
variable. The authentication part is OK. What if we try to access a restricted endpoint? How can we manage to access that endpoint?
1 |
app.get('/me', ensureAuthorized, async function(req, res) { |
2 |
try { |
3 |
const user = await User.findOne({ token: req.token }).exec(); |
4 |
res.json({ |
5 |
type: true, |
6 |
data: user |
7 |
});
|
8 |
} catch (err) { |
9 |
res.json({ |
10 |
type: false, |
11 |
data: "Error occurred: " + err |
12 |
});
|
13 |
}
|
14 |
});
|
When you make a GET
request to /me
, you will get the current user info, but in order to continue with the requested endpoint, the ensureAuthorized
function will be executed.
1 |
function ensureAuthorized(req, res, next) { |
2 |
var bearerToken; |
3 |
var bearerHeader = req.headers["authorization"]; |
4 |
if (typeof bearerHeader !== 'undefined') { |
5 |
var bearer = bearerHeader.split(" "); |
6 |
bearerToken = bearer[1]; |
7 |
req.token = bearerToken; |
8 |
next(); |
9 |
} else { |
10 |
res.send(403); |
11 |
}
|
12 |
}
|
In this function, request headers are intercepted, and the authorization
header is extracted. If a bearer token exists in this header, that token is assigned to req.token
in order to be used throughout the request, and the request can be continued by using next()
. If a token does not exist, you will get a 403 (Forbidden) response. Let's go back to the handler /me
, and use req.token
to fetch user data with this token. Whenever you create a new user, a token is generated and saved in the user model in DB. Those tokens are unique.
We have only three handlers for this simple project. After that, you will see:
1 |
process.on('uncaughtException', function(err) { |
2 |
console.log(err); |
3 |
});
|
The Node.js app may crash if an error occurs. With the above code, that crash is prevented and an error log is printed in the console. And finally, we can start the server by using the following code snippet.
1 |
// Start Server
|
2 |
app.listen(port, function () { |
3 |
console.log( "Express server listening on port " + port); |
4 |
});
|
To sum up:
- Modules are imported.
- Configurations are made.
- Request handlers are defined.
- Middleware is defined in order to intercept restricted endpoints.
- The server is started.
We are done with the back-end service. So that it can be used by multiple clients, you can deploy this simple server application to your servers, or maybe you can deploy in Heroku. There is a file called Procfile
in the project's root folder. Let's deploy our service in Heroku.
Heroku Deployment
You can clone the back-end project from this GitHub repository.
I will not be discussing how to create an app in Heroku; you can refer to this article for creating a Heroku app if you have not done this before. After you create your Heroku app, you can add a destination to your current project by using the following command:
1 |
git remote add heroku <your_heroku_git_url> |
Now you have cloned a project and added a destination. After git add
and git commit
, you can push your code to Heroku by performing git push heroku master
. When you successfully push a project, Heroku will perform the npm install
command to download dependencies into the temp
folder on Heroku. After that, it will start your application, and you can access your service by using the HTTP protocol.
token-based-auth-frontend
In the front-end project, you will see an Angular project. Here, I'll only mention the main sections in the front-end project, because Angular is not something that can be covered within a single tutorial.
You can clone the project from this GitHub repository. In this project, you will see the following folder structure:



We have three components—sign up, profile, and sign in—and an auth service.
Your app.component.html looks like this:
1 |
<!doctype html>
|
2 |
<html lang="en"> |
3 |
<head>
|
4 |
<meta charset="utf-8"> |
5 |
<meta name="viewport" content="width=device-width, initial-scale=1"> |
6 |
<title>Bootstrap demo</title> |
7 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> |
8 |
</head>
|
9 |
<body>
|
10 |
<nav class="navbar navbar-expand-lg bg-body-tertiary"> |
11 |
<div class="container-fluid"> |
12 |
<a class="navbar-brand" href="#">Home</a> |
13 |
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> |
14 |
<span class="navbar-toggler-icon"></span> |
15 |
</button>
|
16 |
<div class="collapse navbar-collapse" id="navbarNav"> |
17 |
<ul class="navbar-nav"> |
18 |
|
19 |
<li class="nav-item"><a class="nav-link" routerLink="/profile">Me</a></li> |
20 |
<li class="nav-item"><a class="nav-link" routerLink="/login">Signin</a></li> |
21 |
<li class="nav-item"><a class="nav-link" routerLink="/signup">Signup</a></li> |
22 |
<li class="nav-item"><a class="nav-link" (click)="logout()">Logout</a></li> |
23 |
</ul>
|
24 |
</div>
|
25 |
</div>
|
26 |
</nav>
|
27 |
|
28 |
<div class="container"> |
29 |
<router-outlet></router-outlet>
|
30 |
</div>
|
31 |
|
32 |
</body>
|
33 |
</html>
|
In the main component file, the <router-outlet></router-outlet>
defines the routes for individual components.
In the auth.service.ts file, we define the AuthService
class, which handles authentication by making API calls to sign in, authenticate, and me API endpoints of the Node.js app.
1 |
import { Injectable } from '@angular/core'; |
2 |
import { HttpClient,HttpHeaders } from '@angular/common/http'; |
3 |
import { Observable } from 'rxjs'; |
4 |
import { tap } from 'rxjs/operators'; |
5 |
|
6 |
@Injectable({ |
7 |
providedIn: 'root' |
8 |
})
|
9 |
export class AuthService { |
10 |
private apiUrl = 'your_node_app_url'; |
11 |
public token: string =''; |
12 |
|
13 |
|
14 |
constructor(private http: HttpClient) { |
15 |
|
16 |
}
|
17 |
|
18 |
|
19 |
signin(username: string, password: string): Observable<any> { |
20 |
const data = { username, password }; |
21 |
return this.http.post(`${this.apiUrl}/signin`, data); |
22 |
}
|
23 |
|
24 |
|
25 |
|
26 |
authenticate(email: string, password: string): Observable<any> { |
27 |
const data = { email, password }; |
28 |
console.log(data) |
29 |
|
30 |
return this.http.post(`${this.apiUrl}/authenticate`, data) |
31 |
.pipe( |
32 |
tap((response:any) => { |
33 |
this.token = response.data.token; // Store the received token |
34 |
localStorage.setItem('token',this.token) |
35 |
console.log(this.token) |
36 |
})
|
37 |
);
|
38 |
}
|
39 |
|
40 |
profile(): Observable<any> { |
41 |
const headers = this.createHeaders(); |
42 |
return this.http.get(`${this.apiUrl}/me`,{ headers }); |
43 |
}
|
44 |
|
45 |
|
46 |
private createHeaders(): HttpHeaders { |
47 |
let headers = new HttpHeaders({ |
48 |
'Content-Type': 'application/json', |
49 |
});
|
50 |
|
51 |
if (this.token) { |
52 |
headers = headers.append('Authorization', `Bearer ${this.token}`); |
53 |
}
|
54 |
|
55 |
return headers; |
56 |
}
|
57 |
|
58 |
logout(): void { |
59 |
|
60 |
localStorage.removeItem('token'); |
61 |
}
|
62 |
|
63 |
|
64 |
}
|
65 |
|
66 |
In the authenticate()
method, we send a POST request to the API and authenticate the user. From the response, we extract the token and store it in both the service's this.token
property and the browser's localStorage
, and then return the response as an Observable
.
In the profile()
method, we make a GET request by including the token in the Authorization header to fetch the user details.
The createHeaders()
method creates HTTP headers that include the authentication token when making authenticated API requests. It adds an Authorization header when the user has a valid token. The token allows the back-end API to authenticate the user.
If authentication is successful, the user token is stored in local storage for subsequent requests. The token is also made available to all components. If authentication fails, we display an error message.
Do not forget to put the service URL in baseUrl
in the above code. When you deploy your service to Heroku, you will get a service URL like appname.herokuapp.com
. In the above code, you will set var baseUrl = "appname.herokuapp.com"
.
The logout function removes the token from local storage.
In the signup.component.ts
file, We implement the signup ()
method, which gets the user-submitted email and password and creates a new user.
1 |
import { Component } from '@angular/core'; |
2 |
import { AuthService } from '../auth.service'; |
3 |
|
4 |
|
5 |
|
6 |
@Component({ |
7 |
selector: 'app-signup', |
8 |
templateUrl: './signup.component.html', |
9 |
styleUrls: ['./signup.component.css'] |
10 |
})
|
11 |
export class SignupComponent { |
12 |
password: string = ''; |
13 |
email: string = ''; |
14 |
|
15 |
|
16 |
constructor(private authService:AuthService){} |
17 |
|
18 |
signup(): void { |
19 |
this.authService.signin(this.email, this.password).subscribe( |
20 |
(response) => { |
21 |
// success response
|
22 |
console.log('Authentication successful', response); |
23 |
|
24 |
},
|
25 |
(error) => { |
26 |
// error response
|
27 |
console.error('Authentication error', error); |
28 |
}
|
29 |
);
|
30 |
}
|
31 |
}
|
1 |
import { Component } from '@angular/core'; |
2 |
import { AuthService } from '../auth.service'; |
3 |
|
4 |
|
5 |
|
6 |
@Component({ |
7 |
selector: 'app-login', |
8 |
templateUrl: './login.component.html', |
9 |
styleUrls: ['./login.component.css'] |
10 |
})
|
11 |
export class LoginComponent { |
12 |
|
13 |
email: string = ''; |
14 |
password: string = ''; |
15 |
|
16 |
constructor(private authService: AuthService) {} |
17 |
|
18 |
login(): void { |
19 |
this.authService.authenticate(this.email, this.password).subscribe( |
20 |
(response) => { |
21 |
// success response
|
22 |
console.log('Signin successful', response); |
23 |
|
24 |
},
|
25 |
(error) => { |
26 |
// error response
|
27 |
console.error('Signin error', error); |
28 |
}
|
29 |
);
|
30 |
}
|
31 |
}
|
The profile component uses the user token to fetch the user's details. Whenever you make a request to a service in the back end, you need to put this token in the headers. The profile.component.ts looks like this:
1 |
import { Component } from '@angular/core'; |
2 |
import { AuthService } from '../auth.service'; |
3 |
@Component({ |
4 |
selector: 'app-profile', |
5 |
templateUrl: './profile.component.html', |
6 |
styleUrls: ['./profile.component.css'] |
7 |
})
|
8 |
|
9 |
export class ProfileComponent { |
10 |
myDetails: any; |
11 |
|
12 |
constructor(private authService: AuthService) { } |
13 |
|
14 |
ngOnInit(): void { |
15 |
this.getProfileData(); |
16 |
}
|
17 |
getProfileData(): void { |
18 |
this.authService.me().subscribe( |
19 |
(response: any) => { |
20 |
this.myDetails = response; |
21 |
console.log('User Data:', this.myDetails); |
22 |
},
|
23 |
(error: any) => { |
24 |
console.error('Error retrieving profile data'); |
25 |
}
|
26 |
);
|
27 |
}
|
In the above code, every request is intercepted, and an authorization header and value are put in the headers. We then pass the user details to the profile.component.html template.
1 |
<h2>User profile </h2> |
2 |
|
3 |
<div class="row"> |
4 |
<div class="col-lg-12"> |
5 |
<p>{{myDetails.data.id}}</p> |
6 |
<p>{{myDetails.data.email}}</p> |
7 |
</div>
|
8 |
</div>
|
Finally, we define the routing in app.routing.module.ts.
1 |
import { NgModule } from '@angular/core'; |
2 |
import { RouterModule, Routes } from '@angular/router'; |
3 |
import { LoginComponent } from './login/login.component'; |
4 |
import { ProfileComponent } from './profile/profile.component'; |
5 |
import { SignupComponent } from './signup/signup.component'; |
6 |
|
7 |
const routes: Routes = [ |
8 |
{path:'signup' , component:SignupComponent}, |
9 |
{path:'login' , component:LoginComponent}, |
10 |
{ path: 'profile', component: ProfileComponent }, |
11 |
];
|
12 |
|
13 |
@NgModule({ |
14 |
imports: [RouterModule.forRoot(routes)], |
15 |
exports: [RouterModule] |
16 |
})
|
17 |
export class AppRoutingModule { } |
As you can easily understand in the above code, when you go to /, the app.component.html page will be rendered. Another example: if you go to /signup, signup.component.html will be rendered. This rendering operation will be done in the browser, not on the server-side.
Conclusion
Token-based authentication systems help you to construct an authentication/authorization system while you are developing client-independent services. By using this technology, you will just focus on your services (or APIs).
The authentication/authorization part will be handled by the token-based authentication system as a layer in front of your services. You can access and use services from any client like web browsers, Android, iOS, or a desktop client.