Introduction
Today, in a short tutorial, we plan to implement the authentication system through Google OAuth2 in a simple Fastify project with TypeScript.
There were several options on the table, but 2 options were more serious than the others. The first option is to use the @fastify/fastify-oauth2 package. The second option is to use Google Auth Library.
But because @fastify/fastify-oauth2 package is under the Fastify ecosystem, We will also use this option.
Prerequisites:
- Node +16.x
- Typescript
Steps:
In this tutorial, we will follow the steps below:
- Register a new project on Google Cloud Console
- Generate application credentials (Client ID, Client Secret)
- Build a simple HTML page as a Frontend side
- Build a simple server with Fastify + Typescript
I gave the basic explanation, so now let's get started!
Step 1: Register new project on Google Cloud Console
In the first step, we need to build our new project on the Google cloud platform.
For this, we go to the Google cloud platform panel and start according to the following sub-steps:
-
Go to this website: Google Cloud Console
Enter the project name (and select your location if you
have some organization) and click on "Create" -
After that, you will get a new message in the notification area saying "Create Project: YOUR_PROJECT_NAME". Open the notification area on the right side and click on "SELECT PROJECT".
-
Now open the navigation from the left side, move your mouse on the "APIs and Services" and click on the "OAuth consent screen".
-
Now you should choose one User Type, select External user type and click on the "CREATE" button.
-
Now you have to enter your application information on this page like below:
- App name: Test Application
- User support email: select your Email Address
- Developer contact information: enter your Email Address
And now click on the "SAVE AND CONTINUE" button.
-
On the next page, You can Add/Remove scopes for your application. As you know, We need to get information such as the user's first name, last name, profile picture and language to store in the database after the user has logged in with Google.
So click on the "ADD OR REMOVE SCOPES" button and select the "…/auth/userinfo.email" and "…/auth/userinfo.profile" scopes from the opened menu and click on the "UPDATE" button at the end of the opened modal. After that.
Now you can click on the "SAVE AND CONTINUE" button to go to the next step. In the last step, you need to introduce some test users that you plan to use for Google login testing. To do this, click on the "ADD USERS" button and enter the email address of the users you want to use and click on the "SAVE" button at the end.
Now click on "SAVE AND CONTINUE" to finish the step 1 process!
OK! Done. Now proceed to the next step to create your application credentials.
Step 2: Generate Application Credentials
In the second step, we need to create our application credentials.
To do this, go to the "Credentials" under the "APIS And Services" menu, click on the "CREATE CREDENTIALS" button on the top bar and select "OAuth Client ID" option.
Now in the newly opened form, set the items as follows:
- Application type: Web application
- Name: Web Client 1 (Or whatever you want)
- Authorized JavaScript Origins: http://localhost:4000
- Authorized Redirect URIs: http://localhost:8000/oauth2/google/callback
After you click on the "CREATE" button, a new modal will be displayed for you with this title: "OAuth client created"
Be sure to copy "Client ID" and "Client secret" and save it in a safe place.
DONE! The second step is over! Now we can start coding.
Step 3: Build an simple HTML page
In the third step, create a new folder named fastify-oauth2
and create 2 other folders inside it named front
and server
.
Open your favorite IDE (I use Visual Studio Code) and enter the front
folder.
Note: Since we are not going to go into details in this tutorial, we will use a very simple HTML page. (But keep in mind that you can create this page in any other framework such as Vue and React)
Now, Create a new html file and named it like index.html
and paste the following codes into it:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Fastify + Typescript - Google OAuth2 Authentication</title>
</head>
<body>
<a href="http://localhost:8000/oauth2/google">
Google Login
</a>
<script>
// This function checks the current url and takes the query parameters
function getUrlVars() {
var url = window.location.href;
var vars = {};
var hashes = url.split("?")[1];
var hash = hashes.split('&');
for (var i = 0; i < hash.length; i++) {
params=hash[i].split("=");
vars[params[0]] = params[1];
}
return vars;
}
// This function create a new cookie
function setCookie(cname, cvalue, exdays) {
const d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
let expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
// This statement will check if access_token query parameter is set or not
// If it was set, it creates a new cookie with the new access token value
if(getUrlVars()["access_token"]) {
setCookie("google_access_token", getUrlVars()["access_token"], 1);
console.log("Logged in successfully!")
}
</script>
</body>
</html>
Now serve this basic page on port 4000 (You can use in-built Live Server if you are using Visual Studio Code OR You can use Live Server package)
Our frontend side is ready to use!
Now we go to the next step, that is to build the server, to move forward together how to implement the Google OAuth2 with Fastify.
Step 4: Create the Fastify + Typescript server from the scratch
Now, open your favorite IDE again and enter the server folder.
Notes: Don't forget to go to the server path in your console terminal:
cd server
We need to initialize the package.json
there, so write this command in the console:
# If you using NPM
npm init -y
# If you using Yarn
yarn init -y
After that, we need to install Fastify and some more dependencies, So write this 2 command in the terminal and hit the enter:
# Dependencies
yarn add fastify @fastify/oauth2 @fastify/cors axios
# Development Dependencies
yarn add -D @types/node tsx typescript
Now initial tsconfig.json file with this command:
npx tsc --init
Replace the content of the created ./server/tsconfig.json
file with the following code:
{
"compilerOptions": {
"target": "es2016",
"experimentalDecorators": true,
"module": "commonjs",
"resolveJsonModule": true,
"sourceMap": true,
"outDir": "./build",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true
}
}
Now add the following scripts to the ./server/package.json
file:
{
"scripts": {
"build": "tsc",
"dev": "tsx watch src/app.ts"
}
}
After installing and configuring the project, now we review the folder structure together.
First, create a folder named src
inside the server
folder and create the following files & folders inside src :
fastify-oauth2
│
└───front
│ │ index.html
│
└───server
│ │───node_modules
│ │
│ └───src
│ │───core
│ | │ createServer.ts
│ │───modules
│ │ |───oauth2
│ │ |───google
│ | | google.route.ts
│ │───providers
│ | │ cors.ts
│ | │ oauth2.ts
│ │ app.ts
│ package.json
│ tsconfig.json
│ yarn.lock
First of all, I have to introduce to you one of the best file and folder structures for Fastify projects.
In this structure, we will have 3 main components:
- Core: In this component, we build all the main structures of our application. Such as building a new server, connecting with the database and...
-
Modules: In this component, we will break down our application into smaller pieces. So if a component needs to be developed, or needs to be accessed by a specific person, we will isolate only its features.
For example, consider the user module. This module needs components such as controller, model, router, etc. So we can separate it piece by piece in this section. Like the example below:
- ./src/modules/user/user.controller.ts
- ./src/modules/user/user.route.ts
- ./src/modules/user/user.model.ts
- ./src/modules/user/user.schema.ts
- ./src/modules/user/user.service.ts
- ...
- Providers: In this component, we can set the main and inclusive plugins in the whole system. such as auth, cors and...
After getting familiar with the structure, we will continue the training.
Open the ./src/app.ts
file and paste the following codes inside it:
import { createServer } from './core/createServer';
async function startServer() {
// Create a new instance of the server
const server = await createServer();
// Also you can save these values in the .env file
// Like POST, HOST, ...
await server.listen({
port: 8000,
host: "localhost",
});
console.log(`App is running on http://localhost:8000`);
}
// Starting the server ...
(async () => {
await startServer();
})();
Open the ./src/core/createServer.ts
file and paste the following codes inside it:
import fastify from 'fastify';
// Import Providers
import {registerCorsProvider} from "../providers/cors";
import {registerGoogleOAuth2Provider} from "../providers/oauth2";
// Import Google OAuth2 Routes
import {googleOAuth2Routes} from "../modules/oauth2/google/google.route";
export async function createServer() {
// Creating a new fastify instance
const app = fastify();
// Register the CORS provider
registerCorsProvider(app);
// Register the Google OAuth2 provider & routes
registerGoogleOAuth2Provider(app);
app.register(googleOAuth2Routes, { prefix: '/oauth2' });
return app;
}
Open the ./src/providers/cors.ts
file and paste the following codes inside it:
import {FastifyInstance} from "fastify";
import Cors from "@fastify/cors";
// Cors Policies Options
const corsOptions = {
// Allow all origins
// VERY IMPORTANT: In response, the server returns an Access-Control-Allow-Origin header with Access-Control-Allow-Origin: *
// which means that the resource can be accessed by any origin. (VERY DANGER!)
// You can read more about in:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
origin: "*"
};
export function registerCorsProvider(app: FastifyInstance) {
app.register(Cors, corsOptions)
}
Open the ./src/providers/oauth2.ts
file and paste the following codes inside it:
import {FastifyInstance, FastifyReply, FastifyRequest} from "fastify";
import OAuth2, {OAuth2Namespace} from "@fastify/oauth2";
// Register a GoogleOAuth2 namespace globally in the fastify instance
declare module 'fastify' {
interface FastifyInstance {
GoogleOAuth2: OAuth2Namespace;
}
}
// Google OAuth2 Options
const googleOAuth2Options = {
// Namespace
name: 'GoogleOAuth2',
// Scopes
scope: ['profile', 'email'],
credentials: {
client: {
// Put in your client id here
id: "<CLIENT_ID>",
// Put in your client secret here
secret: "<CLIENT_SECRET>"
},
// @fastify/oauth2 google provider
auth: OAuth2.GOOGLE_CONFIGURATION
},
// This option will create a new root with the GET method to log in through Google.
// Make sure you don't have any other routes in this path with the GET method.
startRedirectPath: '/oauth2/google',
// Here you specify the Google callback route.
// All logics will be checked after the success login or failure login in this path.
callbackUri: `http://localhost:8000/oauth2/google/callback`,
// The following 2 functions are check in detail whether the input parameters from Google include the state query parameter or not
generateStateFunction: (request: FastifyRequest, reply: FastifyReply) => {
// @ts-ignore
return request.query.state
},
checkStateFunction: (request: FastifyRequest, callback: any) => {
// @ts-ignore
if (request.query.state) {
callback()
return;
}
callback(new Error('Invalid state'))
}
};
export function registerGoogleOAuth2Provider(app: FastifyInstance) {
app.register(OAuth2, googleOAuth2Options)
}
Open the ./src/modules/oauth2/google/google.route.ts file and paste the following codes inside it:
import {FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest} from 'fastify';
export function googleOAuth2Routes (
app: FastifyInstance,
options: FastifyPluginOptions,
done: () => void,
) {
// https://developers.google.com/
// Google OAuth2 JavaScript redirect URI
app.get('/google/callback',async function (request: FastifyRequest, reply: FastifyReply) {
// Get the access token from the Google service and save it into the token value
const { token } = await app.GoogleOAuth2.getAccessTokenFromAuthorizationCodeFlow(request);
// Redirect to our frontend side
// You can get the access token from the URI Query and save it as a cookie in the client browser
reply.redirect("http://localhost:4000/?access_token=" + token.access_token);
});
done();
}
Now, you can start the server with this command:
# If you using NPM
npm run dev
# If you using Yarn
yarn dev
Make sure that the live server of the front side is also running on port 4000.
Now go to this link: http://localhost:4000 and test the Google OAuth2 authentication that we made together in this article!
If you did everything according to the instructions, you should go back to the "http://localhost:4000" page after clicking the "Google Login" button and selecting your Google account, but with the difference that if you open your developer console panel, you will receive the "Logged in successfully!" message.
Also, if you go to the developer console panel and open the "Application" tab, you can see a new cookie with the name "google_access_token" is stored in the cookies section!
Now everything is ready for your Google authenticated user to use the website.
You can access the GitHub repository of this tutorial from the link below
Conclusion
Thank you for accompanying me, if you have any questions, ask them in the comments section and I will answer them as soon as I have time.
If this article was useful for you, I would appreciate it if you could show your reaction 😍
Top comments (2)
Thanks for this article. But How do we logout of the application? Do we destroy the cookie?
Hello dear friend, You're welcome.
First of all, I'm so sorry for my delay.
Every OAuth2 provider only verifies your user who wants to log in via their methods. So users always be part of your community/application before trying to log out from the provider.
For example, If I log in via Google on some website, I will log out whenever I log out from the accounts I used to log in within Google.
But in general, Yes! You can remove the cookie/session to log out your user from your application.
Or, you can create some buttons to redirect a user to Google services (such as Gmail, YouTube, ...)
For example:
OR
Enjoy your time