Chapter 5: Login page. Session and cookie. Google OAuth API. Authentication HOC withAuth. firstGridItem logic in App HOC.
Last updated April 2025.
The section below is a preview of SaaS Boilerplate Book. To read the full text, you will need to purchase the book.
In Chapter 5, you will start with the codebase in the 5-begin folder of our saas repo and end up with the codebase in the 5-end folder.
We will cover the following topics in this chapter:
Login page - LoginButton
Session and cookie - Configure session and mount middleware - Save session to database - Configure and create cookie
Google OAuth API - Express routes for Google OAuth API - Configure passport - verify function - passport and session - Static methods publicFields and signInOrSignUpViaGoogle - getUserApiMethod API method and '/get-user' Express route - Google Cloud Platform - Testing Google OAuth API
Authentication HOC withAuth - getUserApiMethod in withAuth - Redirect logic in withAuth - Render logic in withAuth HOC - Testing withAuth HOC
firstGridItem logic in App HOC
We learned many useful topics in Chapter 4. A particularly useful topic was creating "two project" architecture and building internal and external API infrastructures within this architecture. For example, we defined and built "Getting user by slug API". For this API, req-res cycles are between app, api, and a MongoDB server. We built the more complicated "Uploading file API" that consists of two sub-APIs. The first sub-API, called "Getting signed request API", involves app, api, and an AWS S3 server. The second sub-API, called "Uploading file using signed request API", involves only app and an AWS S3 server, without api.
As you progress through this book, we will practice our API-building skills many more times. We will build both "internal" and "external" API infrastructures. Internal APIs will have req-res cycles in between app, api, and a MongoDB server. External APIs involve external, third-party servers other than MongoDB server. In Chapter 4, we built "Uploading file API", which is an external API, since it involves AWS S3 server.
Building various API infrastructures is one of key skills of a web developer.
Currently, our web application has no way to differentiate beetween a guest (or logged-out) user and a logged-in user. Our primary goal in Chapter 5 is to, finally, add user authentication to our web application. We will implement user authentication using Google OAuth API infrastructure. Then in Chapter 6, we will implement an alternative user authentication method using Passwordless API.
So you can see that adding user authentication to our web application amounts to adding a new external API infrastructure that involves Google OAuth server. As you may have concluded at this point, adding any new data exchange to our web application means adding a new internal or external API infrastructure.
In the first section of this chapter, we will discuss and create a Login page. Then we will discuss the concepts of session and cookie. After that, we will discuss details of the Google OAuth API infrastructure. After that, we will add user authentication by defining a new higher-order component, withAuth.
Login page link
In this section, we will create a new page: Login. The page is relatively simply to implement, since you already implemented the Index and YourSettings pages in previous chapters. Check up the code for the Index page at book/5-begin/app/pages/index.tsx and the code for the YourSettings page at book/5-begin/app/pages/your-settings.tsx. The pattern for these pages can be summarized like this. Let's call such pattern a blueprint for pages:
// some imports go here
type Props = { user: { email: string; displayName: string } };
class Index extends React.Component<Props> {
public static async getInitialProps() {
// some JS/TS code goes here
}
public render() {
return (
<Layout {...this.props}>
<Head>
<title>Index page</title>
<meta name="description" content="This is a description of the Index page" />
</Head>
// some HTML/React/Material-UI code goes here
</Layout>
);
}
}
export default Index;The Login page is similar to both Index and YourSettings pages in the following aspects: - We define data types for Props of the page component. - The Login page contains a Head element and main div element, and the page is wrapped with a Layout component.
The Login page differs from the Index and YourSettings pages in the following aspects: - We don't need to define getInitialProps on the Login page, since we don't need any dynamic data to populate the page's props - Instead of having Material-UI's Button component, the Login page will contain an imported custom component called LoginButton. LoginButton will indeed use the Button component. but we define this component in the next subsection.
The Login page is even more straightforward to define than the Index page:
import Head from 'next/head';
import React from 'react';
import LoginButton from '../components/common/LoginButton';
import Layout from '../components/layout';
class Login extends React.Component {
public render() {
return (
<Layout {...this.props}>
<div style={{ textAlign: 'center', margin: '0 20px' }}>
<Head>
<title>Log in to SaaS boilerplate by Async</title>
<meta
name="description"
content="Login and signup page for SaaS boilerplate demo by Async"
/>
</Head>
<br />
<p style={{ margin: '45px auto', fontSize: '44px', fontWeight: 400 }}>Log in</p>
<p>You’ll be logged in for 14 days unless you log out manually.</p>
<br />
<LoginButton />
</div>
</Layout>
);
}
}
export default Login;Place the above code into a new file, book/5-begin/app/pages/login.tsx.
Note how we wrote a descriptive SEO title and description for the Login page. Why did we do it? It's because, unlike the Index and YourSettings pages, we want search engine bots to crawl the Login page. We want people who search on Google or other search engines to be able to discover our Login page. When we deploy our web app in Chapter 10, the crawled URL for the Login page will be https://saas-app.async-await.com/login. We will also set up proper robots.txt and sitemap.xml files for our app project in Chapter 10.
LoginButton link
Unlike other non-page components that we created so far in this book (Notifier, Confirmer, MenuWithLinks), the LoginButton component has no need for state, because there is no need for storing any temporary data. If you need to remember how we created these non-page components, check up any file inside the book/5-begin/app/components/common/ folder.
The LoginButton component needs no methods except the LoginButton.render method. Create a new file, book/5-begin/app/components/common/LoginButton.tsx, with the following code:
import Button from '@mui/material/Button';
import React from 'react';
class LoginButton extends React.Component {
public render() {
const url = `${process.env.NEXT_PUBLIC_URL_API}/auth/google`;
console.log(url);
return (
<React.Fragment>
<Button variant="contained" color="secondary" href={url}>
<img
src="https://storage.googleapis.com/async-await-all/G.svg"
alt="Log in with Google"
/>
Log in with Google
</Button>
<p />
<br />
</React.Fragment>
);
}
}
export default LoginButton; is called a "non-breaking space" HTML entity. On the browser, it renders as one empty space. HTML entities are special (reserved) strings of characters that start with & and render as some difficult-to-type characters:
https://developer.mozilla.org/en-US/docs/Glossary/Entity
We could have wrapped the text Log in with Google with <span>...</span> and applied styles to it to create space to the left of the text. Instead, we used the "non-breaking space" HTML entity.
We did not discuss the code for the LoginButton component in detail, since we already know how to build even more complicated components. However, there is one important fact to point out. So far in this book, every page we built had a page method that calls an API method to get some dynamic data inside the page's getInitialProps method. We have no getInitialProps method for Login page. Instead of an API method, we use a Button component with an href prop from Material-UI's library. This component renders into an HTML element: anchor <a>. When a user clicks on this anchor element, the browser automatically sends a request with the method GET to the API endpoint ${process.env.URL_API}/auth/google. No need for us to define a page method and API method in addition to <a> anchor element, all we need to do is to define the Express route /auth/google on our api server.
Since we already imported LoginButton to the Login page and used it, it's time to see if our page renders as expected. Since the Login page does not have any methods that send requests to the api server, we can load the Login page without starting our api project.
Start your app project with yarn dev and navigate to http://localhost:3000/login:
The page looks good, but the grid item on the left looks out of place. Later in this chapter, we will update our App HOC so that firstGridItem has a value of false for the Login page and we do not see the left grid item on the Login page. The Login page will ultimately have only one grid instead of two.
The terminal window for your app project prints:
http://localhost:8000/auth/googleThis output is from:
console.log(url);The API endpoint has a proper value; however, we haven't implemented an Express route /auth/google at api project to process this request. We will create this Express route later, in the section "Google OAuth API".
In the next section, we will discuss the concept of session and cookie. It's important to understand these concepts before we discuss and add user authentication to our web application. After that, we will build our user authentication using Google OAuth API.
Session and cookie link
Before we build an infrastructure for user authentication, it is critical to understand the concept of session and cookie.How does a web application identify an end user who loads a page? How does a web application keep an end user logged-in? You can log in to any web application on the internet, close the tab, reopen the tab, and find that you are still logged in. This is called persistent login session and its a common feature of most of web applications on the web today.
The short answer, persistent login session can be achieved by setting up session on the server and cookie on the browser.
When an end user logs in to web application for the first time, the web application saves a so-called cookie object to the end user's browser. When this user later comes back and loads the web application's page into browser's tab, the cookie's name and value are sent to the server with an initial request. The server uses cookie's value to find a matching session document in the database. The session document will contain the user's id, which the server uses to find and send back matching user data back to the browser.
Ok, we understand how a returning user can remain logged in, at least, in theory for now. Let's discuss details of implementation for session/cookie infrastructure.
What is session? As we already mentioned, session is an object that a server can create to store unique user-identifying information. The session object gets created by the server and is not accessible on the browser. When an end user logs in to your web application, the server can create a unique session object. For example, you can save a user's id into session. Then your server can save session object to your database as Session MongoDB document. You could make a persistent login session last for 14 days, thus sparing any user from having to re-log in to your web application for 14 days. You simply need some code on your server that removes Session MongoDB document from database after 14 days.
When a logged-out or completely new user logs in or signs up, respectively, on a web application, our server generates a unique
sessionobject that contains a user's id and saves aSessionMongoDB document to our database. Then our server creates a uniquecookieobject that hasnameandvalue. The value forvalueis generated from the session's id. Our server sends a response to the browser that containscookie, andcookiegets saved to a user's browser.
Later, a logged-in user can close a tab within our web application, come back later, and open our web application in a new browser tab. The user's browser will send a request to our server. This request contains
cookie. Our server decodesvalueof the receivedcookie. Our server finds a matchingSessionMongoDB document by the session id in our database. Inside thissessionobject, there is a user id. Our server finds a matchingUserMongoDB document by the user id and sends user data back to the browser. Thus, the user remains in a logged-in state in a new browser tab; in other words, our web application provides users with a persistent login session.
So how does cookie, that is already saved to the browser, gets sent from the browser to the server so that end user can enjoy a persisten login session? It's always important to remember that app project is Next.js and is capable of rendering pages on both the server and the browser. cookie (name and value) gets sent from the browser to the server:
For client-side rendered pages, because
credentials: 'include'for all requests, we specified this value inside the definition ofsendRequestAndGetResponsemethod that all API methods use in our web application. Openbook/5-begin/app/lib/api/sendRequestAndGetResponse.tsfile and find line with:Object.assign({ method: 'POST', credentials: 'include' }, opts, { headers }),Setting
credentialsvalue toincludemakes request to contain cookies even for cross-origin calls (and calls betweenappandapiprojects is cross-origin):
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentialsFor server-side rendered page, there is request from the browser to
appserver and then there is request between two servers,appserver andapiserver. Original request for page comes from the browser to theappserver first. Soappserver hascookiebecause ofcredentials: 'include'. But how to ensure that request fromappserver toapiserver includescookieas well? Actually, it already does! Because of the code we wrote earlier but promised to explained later. Openbook/5-begin/app/lib/api/sendRequestAndGetResponse.tsfile and find the following code block:if (request && request.headers && request.headers.cookie) { headers.cookie = request.headers.cookie; }And then find how
headersthat containcookieare used:Object.assign({ method: 'POST', credentials: 'include' }, opts, { headers }),So what does the above code mean? It means that we take
cookiefrom the very first request (from the browser to theappserver), which isrequest.headers.cookie. Then we save thiscookievalue toheadersby assigning value toheaders.cookie. Then we use theseheaderswithcookiefor the second request (from theappserver to theapiserver), thus ensuring thatapiserver hascookievalue. We still did not add any code that translatescookievalue into matchingsessionanduserbut we will do so below.

Let's create our very first session and cookie in this book. Look at the above diagram again. Let's outline our work:
When the
YourSettingspage loads,getUserBySlugApiMethodexecutes, and the browser sends a request to theAPIserver. We don't have to add any code here. We already built the "getting user by slug" API.apiserver gets a request (from the browser or from the server) and creates asessionobject, as long as we configured session Express middleware to ourapiExpress server. For session Express middleware to work, we need to configure session and mount session Express middleware onapiserver. You already know about Express middleware, for example, you mounted cors and parser Express middleware earlier in this book like this:
You've reached the end of the Chapter 5 preview. To continue reading, you will need to purchase the book.
Last updated April 2025.