Welcome folks today in this blog post we will be building a firebase admin sdk server side authentication project in node.js and express using javascript. All the full source code of the application will be given below.
Get Started
In order to get started you need to create a new node.js project
by executing the below commands as shown below
npm init -y
This will create the package.json file for the node.js project.Now we need to install the packages using the npm command as shown below
npm i express
npm i cookie-parser
npm i firebase-admin
npm i csurf
npm i ejs
After installing all these libraries now you want to see first of all the directory structure of the full app listed below
And now first of all we need to create the web
app inside the firebase console and enable the email and password auth method and also get the serviceAccount
key json file and store it inside our project directory.
Now basically this code will be different for your own firebase project. Copy this config object we will include it later inside our html files.
Now we need to enable the email and password auth method in the firebase console as shown below
Now after this we need to go to service accounts section and generate our service account key as shown below
And now we need to create the index.js
file for our node.js project. And copy paste the below code into it
index.js
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 |
const cookieParser = require("cookie-parser"); const csrf = require("csurf"); const bodyParser = require("body-parser"); const express = require("express"); const admin = require("firebase-admin"); const serviceAccount = require("./serviceAccount.json"); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: "https://aadharcardscanner-72071.firebaseio.com", }); const csrfMiddleware = csrf({ cookie: true }); const PORT = process.env.PORT || 5000; const app = express(); app.engine("html", require("ejs").renderFile); app.use(express.static("static")); app.use(bodyParser.json()); app.use(cookieParser()); app.use(csrfMiddleware); app.all("*", (req, res, next) => { res.cookie("XSRF-TOKEN", req.csrfToken()); next(); }); app.listen(PORT, () => { console.log(`Listening on http://localhost:${PORT}`); }); |
As you can see in the above code we are importing all the libraries and then we are including the middlewares to the express app including making the static directory and also passing the bodyparser middlewares. And also setting cookieParser and csrf middlewares. And we are also setting the view engine of ejs as html. And then we are setting the cookie for xsrf-token to prevent cross site scripting attacks to the login system.
And now we need to copy paste the routes for the express app. These routes are as shown below
1 2 3 4 5 6 7 8 9 10 11 |
app.get("/", function (req, res) { res.render("index.html"); }); app.get("/login", function (req, res) { res.render("login.html"); }); app.get("/signup", function (req, res) { res.render("signup.html"); }); |
These are the get routes to serve static html files which are present inside the views directory. We are loading the index.html when we open the home page of the app. When we go to /login we load the login and for signup we load the /signup.html.
First of all guys we just need to create the static
directory and inside it just create a mvp.css
file which will hold all the stylesheet code of the application
static/mvp.css
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 |
/* MVP.css v1.5 - https://github.com/andybrewer/mvp */ :root { --border-radius: 5px; --box-shadow: 2px 2px 10px; --color: #118bee; --color-accent: #118bee0b; --color-bg: #fff; --color-bg-secondary: #e9e9e9; --color-secondary: #920de9; --color-secondary-accent: #920de90b; --color-shadow: #f4f4f4; --color-text: #000; --color-text-secondary: #999; --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; --hover-brightness: 1.2; --justify-important: center; --justify-normal: left; --line-height: 150%; --width-card: 285px; --width-card-medium: 460px; --width-card-wide: 800px; --width-content: 1080px; } /* @media (prefers-color-scheme: dark) { :root { --color: #0097fc; --color-accent: #0097fc4f; --color-bg: #333; --color-bg-secondary: #555; --color-secondary: #e20de9; --color-secondary-accent: #e20de94f; --color-shadow: #bbbbbb20; --color-text: #f7f7f7; --color-text-secondary: #aaa; } } */ /* Layout */ article aside { background: var(--color-secondary-accent); border-left: 4px solid var(--color-secondary); padding: 0.01rem 0.8rem; } body { background: var(--color-bg); color: var(--color-text); font-family: var(--font); line-height: var(--line-height); margin: 0; overflow-x: hidden; padding: 1rem 0; } footer, header, main { margin: 0 auto; max-width: var(--width-content); padding: 2rem 1rem; } hr { background-color: var(--color-bg-secondary); border: none; height: 1px; margin: 4rem 0; } section { display: flex; flex-wrap: wrap; justify-content: var(--justify-important); } section aside { border: 1px solid var(--color-bg-secondary); border-radius: var(--border-radius); box-shadow: var(--box-shadow) var(--color-shadow); margin: 1rem; padding: 1.25rem; width: var(--width-card); } section aside:hover { box-shadow: var(--box-shadow) var(--color-bg-secondary); } section aside img { max-width: 100%; } /* Headers */ article header, div header, main header { padding-top: 0; } header { text-align: var(--justify-important); } header a b, header a em, header a i, header a strong { margin-left: 0.5rem; margin-right: 0.5rem; } header nav img { margin: 1rem 0; } section header { padding-top: 0; width: 100%; } /* Nav */ nav { align-items: center; display: flex; font-weight: bold; justify-content: space-between; margin-bottom: 7rem; } nav ul { list-style: none; padding: 0; } nav ul li { display: inline-block; margin: 0 0.5rem; position: relative; text-align: left; } /* Nav Dropdown */ nav ul li:hover ul { display: block; } nav ul li ul { background: var(--color-bg); border: 1px solid var(--color-bg-secondary); border-radius: var(--border-radius); box-shadow: var(--box-shadow) var(--color-shadow); display: none; height: auto; padding: .5rem 1rem; position: absolute; right: 0; top: 1.7rem; width: auto; } nav ul li ul li, nav ul li ul li a { display: block; } /* Typography */ code, samp { background-color: var(--color-accent); border-radius: var(--border-radius); color: var(--color-text); display: inline-block; margin: 0 0.1rem; padding: 0rem 0.5rem; text-align: var(--justify-normal); } details { margin: 1.3rem 0; } details summary { font-weight: bold; cursor: pointer; } h1, h2, h3, h4, h5, h6 { line-height: var(--line-height); } mark { padding: 0.1rem; } ol li, ul li { padding: 0.2rem 0; } p { margin: 0.75rem 0; padding: 0; } pre { margin: 1rem 0; max-width: var(--width-card-wide); white-space: pre-line; } pre code, pre samp { padding: 1rem 2rem; } small { color: var(--color-text-secondary); } sup { background-color: var(--color-secondary); border-radius: var(--border-radius); color: var(--color-bg); font-size: xx-small; font-weight: bold; margin: 0.2rem; padding: 0.2rem 0.3rem; position: relative; top: -2px; } /* Links */ a { color: var(--color-secondary); display: inline-block; font-weight: bold; text-decoration: none; } a:hover { filter: brightness(var(--hover-brightness)); text-decoration: underline; } a b, a em, a i, a strong, button { border-radius: var(--border-radius); display: inline-block; font-size: medium; font-weight: bold; line-height: var(--line-height); margin: 0.5rem 0; padding: 1rem 2rem; } button { font-family: var(--font); } button:hover { cursor: pointer; filter: brightness(var(--hover-brightness)); } a b, a strong, button { background-color: var(--color); border: 2px solid var(--color); color: var(--color-bg); } a em, a i { border: 2px solid var(--color); border-radius: var(--border-radius); color: var(--color); display: inline-block; padding: 1rem 2rem; } /* Images */ figure { margin: 0; padding: 0; } figure img { max-width: 100%; } figure figcaption { color: var(--color-text-secondary); } /* Forms */ button:disabled, input:disabled { background: var(--color-bg-secondary); border-color: var(--color-bg-secondary); color: var(--color-text-secondary); cursor: not-allowed; } button[disabled]:hover { filter: none; } form { border: 1px solid var(--color-bg-secondary); border-radius: var(--border-radius); box-shadow: var(--box-shadow) var(--color-shadow); display: block; max-width: var(--width-card-wide); min-width: var(--width-card); padding: 1.5rem; text-align: var(--justify-normal); } form header { margin: 1.5rem 0; padding: 1.5rem 0; } input, label, select, textarea { display: block; font-size: inherit; max-width: var(--width-card-wide); } input[type="checkbox"], input[type="radio"] { display: inline-block; } input[type="checkbox"]+label, input[type="radio"]+label { display: inline-block; font-weight: normal; position: relative; top: 1px; } input, select, textarea { border: 1px solid var(--color-bg-secondary); border-radius: var(--border-radius); margin-bottom: 1rem; padding: 0.4rem 0.8rem; } input[readonly], textarea[readonly] { background-color: var(--color-bg-secondary); } label { font-weight: bold; margin-bottom: 0.2rem; } /* Tables */ table { border: 1px solid var(--color-bg-secondary); border-radius: var(--border-radius); border-spacing: 0; overflow-x: scroll; overflow-y: hidden; padding: 0; } table td, table th, table tr { padding: 0.4rem 0.8rem; text-align: var(--justify-important); } table thead { background-color: var(--color); border-collapse: collapse; border-radius: var(--border-radius); color: var(--color-bg); margin: 0; padding: 0; } table thead th:first-child { border-top-left-radius: var(--border-radius); } table thead th:last-child { border-top-right-radius: var(--border-radius); } table thead th:first-child, table tr td:first-child { text-align: var(--justify-normal); } /* Quotes */ blockquote { display: block; font-size: x-large; line-height: var(--line-height); margin: 1rem auto; max-width: var(--width-card-medium); padding: 1.5rem 1rem; text-align: var(--justify-important); } blockquote footer { color: var(--color-text-secondary); display: block; font-size: small; line-height: var(--line-height); padding: 1.5rem 0; } /* Custom styles */ |
Now we need to create the index.html
file for the index route
views/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Home page</title> <link rel="stylesheet" href="./mvp.css" /> </head> <body> <main> <h1>Home page</h1> <p>This page is public</p> <a href="/profile">profile</a> </main> </body> </html> |
As you can see it contains a simple heading of home page and a simple button to go to the profile page.
Now we need to make the login.html
file and copy paste the following code
views/login.html
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 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Firebase Server Auth</title> <link rel="stylesheet" href="./mvp.css" /> <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js" defer></script> <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js" defer></script> <script src="https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.js" defer></script> </head> <body> <main> <section> <form id="login"> <label>Login</label> <input type="text" name="login" /> <label>Password</label> <input type="password" name="password" /> <button>Log in</button> </form> </section> </main> </body> </html> |
As you can see we are including some firebase cdn
which is required at the client side. And also it contains a simple user form where the user will login with username and password
And similarly we need to create the signup.html
file and copy paste the below code
views/signup.html
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 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Firebase Server Auth</title> <link rel="stylesheet" href="./mvp.css" /> </head> <body> <main> <section> <form id="signup"> <label>Login</label> <input type="text" name="login" /> <label>Password</label> <input type="password" name="password" /> <button>Sign up</button> </form> </section> <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script> <script src="https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.js"></script> </main> </body> </html> |
Again it contains a simple user html5 signup form where user can register by entering the username and the password
Registering Users
Now guys we will be letting the users signup inside our firebase project. For that just go to signup.html
and write some custom javascript code as shown below
signup.html
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 |
<script> const firebaseConfig = { apiKey: "AIzaSyD5D4hc_-Idvb_nsTZBczhIcEn1jZsBLKo", authDomain: "aadharcardscanner-72071.firebaseapp.com", databaseURL: "https://aadharcardscanner-72071.firebaseio.com", projectId: "aadharcardscanner-72071", storageBucket: "aadharcardscanner-72071.appspot.com", messagingSenderId: "922187200582", appId: "1:922187200582:web:d310eacb1badda448c3247" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE) document .getElementById("signup") .addEventListener("submit", (event) => { event.preventDefault(); const login = event.target.login.value; const password = event.target.password.value; firebase .auth() .createUserWithEmailAndPassword(login, password) .then(({ user }) => { return user.getIdToken().then((idToken) => { return fetch("/sessionLogin", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", "CSRF-Token": Cookies.get("XSRF-TOKEN"), }, body: JSON.stringify({ idToken }), }); }); }) .then(() => { return firebase.auth().signOut(); }) .then(() => { window.location.assign("/profile"); }); return false; }); </script> |
As you can see in the above javascript code we are first of all copy pasting the firebase config object that you received early on. Replace your own firebase config object after that we are initializing the firebase app by passing that config object. And now when the form is submitted by the user by entering the username and password we are getting both these details using the form Submit event handler. And after that we are using the firebase
method createUser with email and password to create a new user inside our system. This returns the tokenId value of the user and here we are making a simple fetch call passing this info to create a new session and letting users go to the profile page directly from the signup page.
Now we need to make the sessionLogin
post request in the index.js
file as shown below
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
app.post("/sessionLogin", (req, res) => { const idToken = req.body.idToken.toString(); const expiresIn = 60 * 60 * 24 * 5 * 1000; admin .auth() .createSessionCookie(idToken, { expiresIn }) .then( (sessionCookie) => { const options = { maxAge: expiresIn, httpOnly: true }; res.cookie("session", sessionCookie, options); res.end(JSON.stringify({ status: "success" })); }, (error) => { res.status(401).send("UNAUTHORIZED REQUEST!"); } ); }); |
As you can see we are getting the id token and converting it to string and then we are setting the expiry time for the token in seconds. And then with the help of firebase admin sdk we are creating the user session cookie by passing the token and expiry time and then it returns the session cookie in the promise. And inside it we are just setting the cookie variable of session and passing the value of the sessionCookie. And lastly we are sending success message back to the client side. And if any error takes place we are sending the message of unauthorized request.
Now we also need to write the /profile
route where the users will be redirected to
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 |
app.get("/profile", function (req, res) { const sessionCookie = req.cookies.session || ""; admin .auth() .verifySessionCookie(sessionCookie, true /** checkRevoked */) .then(() => { res.render("profile.html"); }) .catch((error) => { res.redirect("/login"); }); }); |
As you can see inside this request we are checking the sessionCookie variable if it’s set then we are extracting the value of it or initializing to empty string. And after that we are calling the firebase admin auth() method and passing that sessionCookie value. If it’s a valid cookie then we redirect user t the profile.html page otherwise we redirect them to the login page.
Now we need to write the profile.html
file as shown below
views/profile.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Profile page</title> <link rel="stylesheet" href="./mvp.css" /> </head> <body> <main> <h1>Profile page</h1> <p>This page is private</p> <a href="/sessionLogout">Log out</a> </main> </body> </html> |
As you can see the above page is a protected route or page. And it also contains logout button as well.
Now if you go to the browser now if you see if you enter username and password in the signup page a new user will be created in the firebase console and we will be redirected to the profile page as shown below
And now guys we need to write the logout
route whenever the user presses the logout button he or she must be redirected back to the home page and also the userCookie should be deleted as shown below
index.js
1 2 3 4 |
app.get("/sessionLogout", (req, res) => { res.clearCookie("session"); res.redirect("/login"); }); |
As you can see we are clearing or deleting the cookie using the clearCookie()
method and then we are redirecting the user to the /login
route.
Logging in Users
Now guys we need to write some custom javascript code inside the login.html
to allow existing users in the app to successfully login in as shown below
login.html
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 |
<script> window.addEventListener("DOMContentLoaded", () => { const firebaseConfig = { apiKey: "AIzaSyD5D4hc_-Idvb_nsTZBczhIcEn1jZsBLKo", authDomain: "aadharcardscanner-72071.firebaseapp.com", databaseURL: "https://aadharcardscanner-72071.firebaseio.com", projectId: "aadharcardscanner-72071", storageBucket: "aadharcardscanner-72071.appspot.com", messagingSenderId: "922187200582", appId: "1:922187200582:web:d310eacb1badda448c3247" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE) document .getElementById("login") .addEventListener("submit", (event) => { event.preventDefault(); const login = event.target.login.value; const password = event.target.password.value; firebase .auth() .signInWithEmailAndPassword(login, password) .then(({ user }) => { return user.getIdToken().then((idToken) => { return fetch("/sessionLogin", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", "CSRF-Token": Cookies.get("XSRF-TOKEN"), }, body: JSON.stringify({ idToken }), }); }); }) .then(() => { return firebase.auth().signOut(); }) .then(() => { window.location.assign("/profile"); }); return false; }); }); </script> |
As you can see all the code is the same as we did inside the signup.html but here we are using the firebase admin SignIn()
method where we take the email and the password which is submitted by the user. If the information is correct when he or she is redirected to the profile page or it’s incorrect then he or she stays on the login page.