Custom OAuth Server From Scratch Server for authentication

Custom OAuth Server From Scratch Server for authentication
Warning: This post is not completed yet or is in draft or not-publised yet. Please do not consider this as a final version. It may contain errors or incomplete information.

Custom OAuth Server from scratch

Introduction

OAuth is an open standard for access delegation, commonly used as a way for Internet users to grant websites or applications access to their information on other websites but without giving them the passwords. In this article, we will learn how to create a custom OAuth server using Node.js and Express.js with MongoDB and Inertia.js for client/views.

I am writing this article to demostrate only and highly focusing on KISS (Keep It Simple and Stupid). So, begginers or anyone reading this can understand the picture of it easily.

Prerequisites

Before we get started, make sure you have the following installed on your machine:

  • Node.js
  • TypeScript (Recommended, but not using in this time)
  • MongoDB
  • Express.js (Node.js web application framework)
  • Vue & Inertia.js (SSR approach)

Step 1: Create a new Node.js project

First, create a new Node.js project using the following command:

mkdir custom-oauth-server
cd custom-oauth-server
npm init -y

Step 2: Install the required dependencies

Next, install the required dependencies for our project:

npm install express mongoose passport passport-oauth2-client-password

Step 3: Create a new Express.js server

Create a new file called server.js and add the following code:

const express = require('express');
const mongoose = require('mongoose');
const passport = require('passport');
const ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;

const app = express();

app.use(express.json());

mongoose.connect('mongodb://localhost:27017/oauth', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const Client = mongoose.model('Client', new mongoose.Schema({
  clientId: String,
  clientSecret: String,
}));

passport.use(new ClientPasswordStrategy(
  function(clientId, clientSecret, done) {
    Client.findOne({ clientId: clientId, clientSecret: clientSecret }, function(err, client) {
      if (err) { return done(err); }
      if (!client) { return done(null, false); }
      return done(null, client);
    });
  }
));

app.post('/oauth/token', passport.authenticate('oauth2-client-password', { session: false }), (req, res) => {
  res.json({ access_token });
});

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

Step 4: Create a new MongoDB database

Create a new MongoDB database called oauth and add a new collection called clients with the following fields:

  • clientId (String)
  • clientSecret (String)

Step 5: Start the Express.js server

Start the Express.js server using the following command:

node server.js

Step 6: Test the OAuth server

You can test the OAuth server using Postman or any other API testing tool. Send a POST request to http://localhost:3000/oauth/token with the following parameters:

  • client_id (String)
  • client_secret (String)

If the client credentials are valid, the server will return an access token.

Step 7: Implement the client-side view using Inertia.js

Create a new Vue.js project using the following command:

npx create-vue-app client

Install the required dependencies for Inertia.js:

npm install @inertiajs/inertia @inertiajs/inertia-vue @inertiajs/progress vue

Create a new file called app.js and add the following code:

Following SSR approach using Inertia.js

import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue';

createInertiaApp({
  resolve: name => require(`./src/views/${name}`),
  setup({ el, App, props }) {
    createApp({ render: () => h(App, props) }).mount(el);
  },
});

Create a new file called index.vue in src/views and add the following code:

<template>
  <div>
    <h1>OAuth Server</h1>
    <form @submit.prevent="submit">
      <input type="text" v-model="clientId" placeholder="Client ID">
      <input type="text" v-model="clientSecret" placeholder="Client Secret">
      <button type="submit">Submit</button>
    </form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      clientId: '',
      clientSecret: '',
    };
  },
  methods: {
    async submit() {
      const response = await fetch('http://localhost:3000/oauth/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          client_id: this.clientId,
          client_secret: this.clientSecret,
        }),
      });
      const data = await response.json();
      console.log(data);
    },
  },
};
</script>

Step 8: Start the application

Start the application using the following command:

npm run dev

You can now access the application at http://localhost:3000. It should display a form with two input fields for the client ID and client secret. When you submit the form, it will send a POST request to the OAuth server and return an access token.

Step 9: Implement Users and Scopes

You can extend the OAuth server to support users and scopes by adding the following fields to the Client model:

  • userId (String)
  • scopes (Array)

You can then update the ClientPasswordStrategy to validate the user and scopes when authenticating the client.

passport.use(new ClientPasswordStrategy(
  function(clientId, clientSecret, done) {
    Client.findOne
      ({ clientId: clientId, clientSecret: clientSecret }, function(err, client) {
      if (err) { return done(err); }
      if (!client) { return done(null, false); }
      if (client.userId !== req.body.user_id) { return done(null, false); }
      if (!client.scopes.includes(req.body.scope)) { return done(null, false); }
      return done(null, client);
    });
  }

));

You can then update the POST request to include the user_id and scope parameters.

const response = await fetch('http://localhost:3000/oauth/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    client_id: this.clientId,
    client_secret: this.clientSecret,
    user_id: this.userId,
    scope: this.scope,
  }),
});

You can now test the OAuth server with users and scopes.

Why Custom OAuth Server?

  • Customization: You can customize the OAuth server to meet your specific requirements.
  • Security: You have full control over the security of the OAuth server.
  • Scalability: You can scale the OAuth server as needed to handle a large number of clients and users.
  • Integration: You can integrate the OAuth server with other systems and services.

If you want to create an OAuth server for a private organization, you can customize the server to meet the organization’s specific requirements and integrate it with the organization’s existing systems and services. For example, government organizations, financial institutions, and healthcare providers can create custom OAuth servers to secure their data and services without relying on third-party providers.

Conclusion

In this article, we learned how to create a custom OAuth server using Node.js and Express.js with MongoDB. We also learned how to authenticate clients using the OAuth 2.0 client password strategy. I hope you found this article helpful. If you have any questions or feedback, feel free to reach out to me.

If you have any questions or feedback, feel free to reach out to me on Github or Email Me.

© Md. Mahabub Alam