OAuth 2 is a popular protocol for authentication and authorization, allowing users to authorize access to their data without the need to share sensitive passwords and other credentials with third-party applications.
While this makes Oauth 2 a great option for implementing single sign-on (SSO) in your Platformatic application, implementing OAuth 2 and configuring it correctly can be a complex task, especially when integrating it with different frameworks and applications.
In this article, we will delve into the implementation of OAuth2 Login with a Platformatic application and GitHub, leveraging the power of OAuth 2.0 in handling access tokens, securely authenticating users and managing resource access.
Brief Insights Into OAuth2
OAuth2 is an authorization framework that revolutionized how applications obtain limited access to user accounts on various HTTP services. Popular platforms such as Facebook and GitHub rely on OAuth2 to delegate user authentication to the hosting service and authorize third-party applications to access user accounts securely.
OAuth 2.0, also known as "Open Authorization," emerged as the industry standard for online authorization in 2012, replacing its predecessor, OAuth 1.0. It empowers websites and applications to access resources hosted by other web apps on behalf of users, without compromising their credentials. By providing consented access and controlling the actions performed by client applications, OAuth 2.0 ensures secure and granular access to user resources.
The key principles of OAuth 2.0 revolve around authorization rather than authentication, with OAuth 2.0 serving as a means of granting access to resources, such as remote APIs or user data. OAuth 2.0 utilizes Access Tokens, which represent the authorization granted to access resources on behalf of end-users.
Although no specific format is defined for Access Tokens, the JSON Web Token (JWT) format is commonly used, allowing token issuers to include data within the token itself. Additionally, Access Tokens may have expiration dates for enhanced security.
Platformatic DB: An Overview
Platformatic DB is designed to simplify the creation of CRUD APIs, eliminating the need to write code manually. It offers the capability to generate both OpenAPI and GraphQL schemas directly from your database, providing a streamlined approach to API development.
Platformatic DB stands out from similar tools by offering customization options through Node.js and Fastify plugins, leveraging the integration with Platformatic Service.
With Platformatic DB, you can eliminate the hassle of creating Create-Read-Update-Delete (CRUD) APIs from scratch and automatically generate OpenAPI/REST API and GraphQL API based on your SQL schema, saving valuable development time.
The tool supports multiple databases, including;
SQLite
MySQL
MariaDB
PostgreSQL
Setting up the Required Dependencies
Before implementing OAuth2 login with GitHub in your Platformatic application, you will need to set up the necessary dependencies. These dependencies will enable your application to handle the OAuth2 flow and interact with GitHub's authentication endpoints. Follow these steps to get started:
Create a New Platformatic Project
To create a new Platformatic project using the Platformatic CLI;
1. Open your terminal and run the following command:
npm create platformatic@latest
2. The CLI will initiate an interactive command-line tool to guide you through the project setup process. For this guide, select the following:
- Which kind of project do you want to create? => DB
- Where would you like to create your project? => Github-Oauth
- Do you want to create default migrations? => Yes
- Do you want to create a plugin? => Yes
- Do you want to use TypeScript? => No
- Do you want to install dependencies? => Yes (this may take a while)
- Do you want to apply the migrations? => No
- Do you want to generate types? => Yes
- Do you want to create the GitHub action to deploy this application to Platformatic Cloud dynamic workspace? => No
- Do you want to create the GitHub action to deploy this application to Platformatic Cloud static workspace? => No
Once the wizard completes, you will have a Platformatic app project created in the "Github-Oauth" folder. It will include sample migration files and a sample plugin script.
To navigate to the project directory using the VS Code terminal, you can use the following command:
cd Github-Oauth
Use the following command to apply migrations to the project:
npx plt db migrations apply
Run the following command to install the necessary dependencies in the project:
npm i --save fastify@4.19.2 @platformatic/sql-mapper@0.30.0 @platformatic/sql-graphql@0.30.0
Include the Fastify OAuth2 Library in your Project
Use a package manager like npm to install an OAuth2 library for your Platformatic project. We will use the fastify-oauth2 library, which is a wrapper of the simple-oauth2
library. To install, run this command on your terminal:
npm i @fastify/oauth2
Configuring GitHub as an OAuth2 Provider
To enable OAuth2 login with GitHub in your Platformatic application, you will need to configure GitHub as an OAuth2 provider. This involves creating a GitHub OAuth application and obtaining the necessary credentials (Client ID and Client Secret) for your application to authenticate with GitHub.
Follow these steps to configure GitHub as an OAuth2 provider:
1. Create a GitHub OAuth Application:
Log in to your GitHub account and go to the "Settings" page.
In the left sidebar, click on "Developer settings" and then select "OAuth Apps."
- Click on the "New OAuth App" button to create a new OAuth application.
2. Provide Application Details:
- Fill in the required details for your OAuth application, such as the application name, homepage URL, and callback URL.
The callback URL is the URL where GitHub will redirect users after successful authentication. In this case, it should point to the endpoint in your Platformatic application that handles the callback.
Use the following values for the homepage URL and callback URL respectively
“
http://127.0.0.1:3042/”
and“
http://127.0.0.1:3042/login/github/callback”
.
3. Obtain Client ID and Client Secret:
GitHub will provide you with a Client ID and Client Secret after creating the OAuth application.
These credentials are unique to your application and must be kept confidential. Do not share them publicly or expose them in your application's client-side code.
Note: Keep your Client ID and Client Secret close as we would be needing them in the next section of the article.
Building The OAuth2 Request
We have to build the request to the server which will authorize our service as a granted client. To achieve this, head over to your IDE and navigate to the Github-Oauth directory. In the plugin.js
file, add the following code:
/// <reference path="./global.d.ts" />
"use strict";
const fastifyOauth2 = require("@fastify/oauth2");
/** @param {import('fastify').FastifyInstance} app */
module.exports = async function (app, options) {
const startRedirectPath = `/login/github`;
// TODO understand why it is using a full URL
const callbackUri = `http://127.0.0.1:3042/login/github/callback`;
await app.register(fastifyOauth2, {
name: github,
credentials: {
client: {
id: options.clientId,
secret: options.clientSecret,
},
auth: fastifyOauth2.GITHUB_CONFIGURATION,
},
startRedirectPath,
callbackUri,
});
app.get(`/login/github/callback`, async function (request, reply) {
const { token } = await this.github.getAccessTokenFromAuthorizationCodeFlow(request);
console.log(`token.access_token`);
return { access_token: token.access_token };
});
};
Navigate to your “platformatic.db.json” config file and replace its content with the following :
"$schema": "https://platformatic.dev/schemas/v0.29.0/db",
"server": {
"hostname": "{PLT_SERVER_HOSTNAME}",
"port": "{PORT}",
"logger": {
"level": "{PLT_SERVER_LOGGER_LEVEL}"
}
},
"db": {
"connectionString": "{DATABASE_URL}",
"graphql": true,
"openapi": true
},
"watch": {
"ignore": [
"*.sqlite",
"*.sqlite-journal"
]
},
"migrations": {
"dir": "migrations"
},
"plugins": {
"paths": [{
"path": "plugin.js",
"options": {
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET"
}
}]
},
"types": {
"autogenerate": true
}
Now, when you run npm start
in your terminal to start your Platformatic application server, you should see a URL as your localhost listening on port 3042.
ctrl + click the link to open the link in your browser which will redirect you to a Platformatic landing page.
Retrieving a Public Access-Token
On the address bar of your browser, navigate to the URL- “127.0.0.1:3042/login/github
” which we specified as the startRedirectPath
in the plugin.js
file. You will be directed to a GitHub login modal where you will need to input your login credentials.
Upon successful login, you will be redirected and your public access token will be returned on the browser page. It is simply a token that contains all the data about your GitHub profile that is publicly accessible– it should look similar to this:
/// <reference path="./global.d.ts" />
"use strict";
const fastifyOauth2 = require("@fastify/oauth2");
const { request } = require("undici");
async function getGitHubUserDetails(token) {
const res = await request("https://api.github.com/user", {
headers: {
"User-Agent": "platformatic-authentication-server",
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token.access_token}`,
"X-GitHub-Api-Version": "2022-11-28",
},
});
return res.body.json();
}
/** @param {import('fastify').FastifyInstance} app */
module.exports = async function (app, options) {
const startRedirectPath = `/login/github`;
// TODO understand why it is using a full URL
const callbackUri = `http://127.0.0.1:3042/login/github/callback`;
await app.register(fastifyOauth2, {
name: “github”,
credentials: {
client: {
id: options.clientId,
secret: options.clientSecret,
},
auth: fastifyOauth2.GITHUB_CONFIGURATION,
},
Scope: ["user:email", "read:user"],
startRedirectPath,
callbackUri,
});
app.get(`/login/github/callback`, async function (request, reply) {
const { token } = await this.github.getAccessTokenFromAuthorizationCodeFlow(request);
const user = await getUserDetails(token);
return { user };
});
};
The updated code provided for the plugin.js file will now return user data from GitHub. Replace the placeholders YOUR_CLIENT_ID and YOUR_CLIENT_SECRET with your Client ID and Client Secret.
The getGitHubUserDetails
function has now been added, retrieving user details from the GitHub API using the access token. It sends an authenticated request to the GitHub /user endpoint, including the access token in the Authorization header. The response body is returned as JSON.
In the app.get
function for the callback route, after obtaining the access token, the getUserDetails function is called to retrieve the user data from GitHub.
The user object is returned as the response, which contains the user details retrieved from the GitHub API.
Filtering the Response Data
To filter the returned user data from the public access token, we will need to make some changes to our plugin.js file
. Replace the already existing code with the code below:
/// <reference path="./global.d.ts" />
"use strict";
const fastifyOauth2 = require("@fastify/oauth2");
const { request } = require("undici");
async function getGitHubUserDetails(token) {
const res = await request("https://api.github.com/user", {
headers: {
"User-Agent": "platformatic-authentication-server",
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token.access_token}`,
"X-GitHub-Api-Version": "2022-11-28",
},
});
const user = await res.body.json();
return {
fullName: user.name,
email: user.email,
};
}
/** @param {import('fastify').FastifyInstance} app */
module.exports = async function (app, options) {
const startRedirectPath = `/login/github`;
// TODO understand why it is using a full URL
const callbackUri = `http://127.0.0.1:3042/login/github/callback`;
await app.register(fastifyOauth2, {
name: ’github’,
credentials: {
client: {
id: options.clientId,
secret: options.clientSecret,
},
auth:fastifyOauth2.GITHUB_CONFIGURATION,
},
scope: ["user:email", "read:user"],
startRedirectPath,
callbackUri,
});
app.get(`/login/github/callback`, async function (request, reply) {
const { token } = await this.github.getAccessTokenFromAuthorizationCodeFlow(request);
const user = await getGitHubUserDetails(token)
return { user };
});
};
The getGitHubUserDetails
function remains the same, retrieving user details from the GitHub API.
After receiving the response from the GitHub API, the code filters the user data and creates a customized response object.
In this example, the response includes the user's full name and email. You can modify this according to your specific requirements.
Storing User Login Data
To store user login data in a database, we can update the existing code in the plugin.js
file. Replace the existing code with the following:
/// <reference path="./global.d.ts" />
"use strict";
const fastifyOauth2 = require("@fastify/oauth2");
const { request } = require("undici");
async function getGitHubUserDetails(token) {
const res = await request("https://api.github.com/user", {
headers: {
"User-Agent": "platformatic-authentication-server",
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token.access_token}`,
"X-GitHub-Api-Version": "2022-11-28",
},
});
const user = await res.body.json();
return {
fullName: user.name,
email: user.email,
};
}
/** @param {import('fastify').FastifyInstance} app */
module.exports = async function (app, options) {
const startRedirectPath = `/login/github`;
const callbackUri = `http://127.0.0.1:3042/login/github/callback`;
await app.register(fastifyOauth2, {
name: “github”,
credentials: {
client: {
id: "YOUR_CLIENT_ID",
secret: "YOUR_CLIENT_SECRET",
},
auth: fastifyOauth2.GITHUB_CONFIGURATION,
},
scope: ["user:email", "read:user"],
startRedirectPath,
callbackUri,
});
app.get(`/login/github/callback`, async function (request, reply) {
const { token } = await this.github.getAccessTokenFromAuthorizationCodeFlow(request);
const user = await getGitHubUserDetails(token);
const dbUsers = await app.platformatic.entities.user.find({
where: {
providerId: { eq: user.providerId }
}
});
if (dbUsers.length === 0) {
await app.platformatic.entities.user.save({ input: user });
}
return { user };
});
};
Then paste the following code into the 001.do.sql file:
-- Add SQL in this file to create the database tables for your API
CREATE TABLE user (
id INTEGER PRIMARY KEY,
fullName TEXT,
email TEXT,
token OBJECT,
)
Run the command npx plt db migrations apply to apply migrations to the project. Then, run npm start
in your terminal to start your Platformatic application server. In the updated code, we have added a few additional lines to store user data in a database:
We fetch the user details from the GitHub API as before.
If no matching users are found, we use the
app.platformatic.entities.user.save
method to save the user data to the database.
Make sure to replace "YOUR_CLIENT_ID" and "YOUR_CLIENT_SECRET" in the code with your actual GitHub OAuth2 client ID and ClientSecret.
By implementing these changes, the user data will be stored in your Platformatic application's database. You can access the stored user data by navigating to the user URL http://127.0.0.1:3042/user where users are stored.
Debugging SQLite Migration Errors
If you encounter an SQLite error while running migrations in your Platformatic application, there is a simple debug solution. First, run the following command in your terminal:
npx plt db migrations apply -t 0
This command will apply the initial migration and set the migration target to zero. Once this step is complete, you can run your migrations again using:
npx plt db migrations apply
Finally, start your server to ensure that the migrations are successfully applied and your application is running smoothly. This debug process helps resolve SQLite-related migration errors, ensuring the proper functioning of your Platformatic application.
Wrapping Up
Throughout this article, we have explored the principles of OAuth2, learned how to configure GitHub as an OAuth2 provider, and demonstrated the steps to build an OAuth2 request. We have also covered how to store user login data in a database for further use.
You can refer to this GitHub repository to enhance your understanding and explore practical implementation details. This repo provides a comprehensive example of integrating OAuth2 login with GitHub and Platformatic, complete with code samples and additional resources.
Implementing OAuth2 with GitHub and Platformatic offers a secure and convenient way to authenticate users and access protected resources. Remember to adapt the implementation to your specific use cases and security requirements. With OAuth2, you can build robust and scalable applications that provide a seamless and trusted user experience.