Securing user data and enabling seamless authentication are paramount for any application. OAuth 2.0, a widely adopted authorization framework, has emerged as a robust solution to address these challenges. Understanding and implementing OAuth 2.0 can significantly enhance the security and user experience of your application.

This article aims to demystify the OAuth 2.0 protocol and provide developers with a comprehensive guide on seamlessly integrating it into their applications.

What Benefits Brings OAuth 2.0 Protocol?

When you open a new site where registration is needed, you often get bored with this process. At least, you have to specify your mobile number and/or email address, password, and, in some cases - additional information. Some users may leave such applications to avoid messing with registration. Nowadays, we have the following standards for user experience: faster and simpler. And OAuth 2.0 can help us with this.

But what does OAuth stand for? It is “Open authentication,” an industry-standard protocol designed to allow an application or website to access some resources from platforms like Google, Facebook, Microsoft, or Github.

What are the pros of OAuth 2.0?

  • User consent: Users grant you application access to some information from their accounts.
  • No passwords: Users don’t have to create, remember, or store login credentials somewhere.
  • Flexibility: You have various options for available authorization flows.

How To Implement OAUTH2 Protocol Into Your Application? Backend

OAuth 2.0 is now the de facto industry standard for online authorization.

This framework specifies several authorization types for different use cases, the most popular of which are

  1. Authorization Code
  2. PKCE (Proof Key of Code Exchange)
  3. Client credentials
  4. Device code
  5. Refresh Token

You can choose any or a combination of these.

We’re going to describe a server-side implementation of the most popular of these - Authorization Code Flow

Here we see an abstract diagram for OAuth2.0 with Authorization Code Flow:

As you can see in the diagram, there are basically three steps in this type of flow.Step 1. Get authorization codeTo implement OAuth2.0, you first need to register your application and obtain client credentials, in our case client_id and client_secret. To receive an authorization code, client should make a POST request:

POST https://your_server/api/v1/authorization?
state=someState
&response_type=someType
&client_id=someClientId
&redirect_uri=someRedirectUri

Where:state - a randomly generated unique value (required)response_type - must include code for Authorization Code flow (required)client_id - client id of your application   (required)redirect_uri - the redirect_uri of your app, where authentication responses can be sent and received by your app. It must exactly match one of the redirect URIs you registered in the portal, except it must be URL-encoded (required)With request body with user’s credentials:

```JSON
{
 "password": "string",
 "email": "string”
} 

This request should be validated on the server side (is a client with this ID existent and active, are user existent and credentials correct, is redirect uri correct for this client, etc.). If all validations are successful, you will get a response with status code 200 and a body.

```json
{
 "code": "string",
 "state": "string",
 "expiredAt": "timestamp",
 "createdAt": "timestamp"
}

Where:

  • code - the unique temporary authorization code
  • state -  unique state, which was provided in the request
  • createdAt - date and time when code was created
  • expiredAt - date time when code will be expired

Note that the recommended lifetime for temporary code is 10 minutes, but you can adjust it for your current use case.

Step 2. Exchange the temporary code for an access token

Once you've received the authorization_code and the user has given you permission and provided credentials, you can exchange it for a token to access the resource. Redeem the code by sending a POST request to the /tokens endpoint.

POST https://your_server/api/v1/tokens?
code=temporaryCode
&grant_type=authorization_code
&client_id=someClientId
&client_secret=someClientId
&redirect_uri=someRedirectUri

Where:

  • code - the unique temporary authorization code that you received at the previous step (required)
  • grant_type - must include authorization_code (required)
  • client_id - client id of your application (required)
  • client_secret - client secret of your application (required)
  • redirect_uri - the redirect_uri of your app, where your app can send and receive authentication responses.

It must match the one you provided in the previous stem (required).

This example shows a successful response:

```json
{
 "access_token": "string",
 "expired_at": "timestamp"
}

Where:

  • access_token - a requested token that should be used for fetching the user's data
  • expired_at - date and time when the access token will be expired. As in the case with the lifetime of temporary code, you can manage this access token’s lifetime for your use case.

Step 3. Use the access token

Now that you have successfully obtained an access_token, you can use it in requests to web APIs by including it in “Authorization” the header:

GET https://your_server/api/v1/users/account
Authorization: Bearer access_token

Note that this is just one of the possible implementations, and you can customize the requests and responses for your use cases.

Planning to create an app?
Share your idea and we will contact you within 24 hours.

Contact us

How To Implement OAUTH2 Protocol Into Your Application? Frontend

In this part, we will help you to implement the 0Auth 2.0 protocol with authorization code flow.

Authorization code flow. In this flow, you exchange an authorization code for an access token by client’s public and secret keys. Everything is pretty simple with a public key (client ID) - we can handle and send it from the client-side (browser), but how do we keep the secret key confidential and not compromise it? Let’s find it out in the implementation section.

Implementation

How to implement OAuth2 server? An additional auth server would be needed to keep the confidential key secret. But don’t be afraid, it is not so complicated. To simplify things, let’s use the Express.js framework for Node.js web applications and Passport.js - authentication middleware.

Step 1. Obtain credentials

To begin with OAuth 2.0 implementation, you would need to get some data from your authentication provider. You have to get the following:

  • Client ID (public key): A key that can be exposed on your client-side application.
  • Client secret key: A secret key that should be hard-coded or added as an environment’s variable on your authentication server.
  • Authorization URL: An authentication provider’s web app URL where the user will request a one-time authentication code.

Token URL: An authentication provider’s web app URL or API endpoint where your server will exchange an obtained authentication code for an access token.

Step 2. Set up the project for the authentication server

Let’s start by creating a new Express.js application using the Nx build system or your preferred method. Navigate to a folder where you want to create a project (a new folder will be created) and run the following command in the terminal.

bash

npx create-nx-workspace --preset=express

You will have to give consent for workspace creation, then specify a folder name for the workspace (your project folder) and then a name for your application.

Step 3. Install dependencies (Passport.js and others)

To proceed with authorization, Passport.js must be installed along with the OAuth 2.0 strategy provider and packages for handling a session and cookies. If you know that your app and server will have different origins, you must install the cors package to handle cross-origin requests.

Open the terminal in the project directory and run the following commands:

bash

npm install passport passport-oauth2 express-session cookie-parser dotenv cors
npm install --save-dev @types/passport-oauth2 @types/express-session @types/cookie-parser @types/cors

Now, you should have all the dependencies. You can also add a start script to run it with npm. After all manipulations, your package.json file should look like this:

package.json

{
 "name": "@auth-partner/auth-server",
 "version": "0.0.1",
 "license": "UNLICENSED",
 "scripts": {
   "start": "nx serve auth-server"
 },
 "private": true,
 "dependencies": {
   "axios": "^1.0.0",
   "cookie-parser": "^1.4.6",
   "cors": "^2.8.5",
   "dotenv": "^16.3.1",
   "express": "^4.18.1",
   "express-session": "^1.17.3",
   "passport": "^0.7.0",
   "passport-oauth2": "^1.7.0",
   "tslib": "^2.3.0"
 },
 "devDependencies": {
   "@nx/eslint": "17.2.4",
   "@nx/eslint-plugin": "17.2.4",
   "@nx/express": "17.2.4",
   "@nx/jest": "17.2.4",
   "@nx/js": "17.2.4",
   "@nx/node": "17.2.4",
   "@nx/webpack": "17.2.4",
   "@nx/workspace": "17.2.4",
   "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
   "@svgr/webpack": "^8.0.1",
   "@swc-node/register": "~1.6.7",
   "@swc/core": "~1.3.85",
   "@types/express": "4.17.13",
   "@types/jest": "^29.4.0",
   "@types/node": "18.16.9",
   "@typescript-eslint/eslint-plugin": "^6.9.1",
   "@typescript-eslint/parser": "^6.9.1",
   "eslint": "~8.48.0",
   "eslint-config-prettier": "^9.0.0",
   "jest": "^29.4.1",
   "jest-environment-node": "^29.4.1",
   "nx": "17.2.4",
   "prettier": "^2.6.2",
   "react-refresh": "^0.10.0",
   "ts-jest": "^29.1.0",
   "ts-node": "10.9.1",
   "typescript": "~5.2.2",
   "url-loader": "^4.1.1"
 }
}

Once the project is created, we can proceed with setting up Express.js, Passport.js, and its OAuth 2.0 strategy and auth routes.

Step 4. Configure Express.js application

First, let’s configure the Express.js application. If your app and auth server are not on the same origin, we would need cors set up. Also, we will configure a session and cookies. Please note that if you have HTTP protocol, you don’t have to mess with ‘secure’ and ‘sameSite.’ But we assume that the production stage will have HTTPS protocol so that this situation will be handled in the code.

For that, let’s add some variables to the .env file

.env

# Environment stage: production considered safe (https)
NX_STAGE=development
# App port considered to be 80 on production environment
NX_APP_PORT=3333
# Session secret can be any string for your local memory store
NX_SESSION_SECRET="your-session-secret"
# Partner origin (needed for CORS origin)
NX_PARTNER_ORIGIN=http://localhost:4202
# Partner secret key (you can store it here or just hard-code)
NX_PARTNER_SECRET="partner-secret"

Then let’s proceed with a session and cookies, open the app folder, and find the main.ts file in the src folder. Let’s add some configurations with app.use function:

Typescript, main.ts

import cookieParser from 'cookie-parser'
import cors from 'cors'
import express from 'express'
import session from 'express-session'

const app = express()

app.use(
 cors({
   credentials: true,
   origin: process.env.NX_PARTNER_ORIGIN,
 }),
)

const isSecureEnv = process.env.NX_STAGE === 'production'

if (isSecureEnv) {
 app.set('trust proxy', 1)
}

const sessionConfig = {
 secret: process.env.NX_SESSION_SECRET,
 saveUninitialized: true,
 resave: false,
 cookie: {
   secure: isSecureEnv,
   sameSite: isSecureEnv ? ('none' as const) : ('lax' as const),
   maxAge: 365 * 24 * 60 * 60 * 1000,
 },
}

app.use(cookieParser())
app.use(session(sessionConfig))
// Here passport.js code will be initialized later, then auth routes 

// Listening section
const port = process.env.NX_APP_PORT || 3333
const server = app.listen(port, () => {
 console.log(`Listening at http://localhost:${port}/api`)
});
server.on('error', console.error)

Now it is time to initialize Passport.js with the OAuth 2.0 strategy.

Step 5. Initialize Passport.js and add OAuth 2.0 strategy

Let’s set up a strategy for authorization code flow. We will tell Passport to use a state parameter to mitigate CSRF attacks. Passport.js handles the state generation and validation automatically. Let’s have a look at the initialization process:

Typescript, main.ts

// previous imports 
import passport from 'passport'
import OAuth2Strategy, { VerifyCallback } from 'passport-oauth2'

// session and cookies initialization 

app.use(cookieParser())
app.use(session(sessionConfig))

// new lines to be added
app.use(passport.initialize())
app.use(passport.session())
passport.use(new OAuth2Strategy({
    // link to OAuth 2.0 provider's app that will request an authorization code
    authorizationURL: 'https://www.example.com/oauth2/authorize',
    // OAuth 2.0 provider's API endpoint where you exchange the code for a token
    tokenURL: 'https://www.example.com/oauth2/token',
    // Client ID (public key). Can be hard-coded, stored as .env variable or passed from app
    clientID: EXAMPLE_CLIENT_ID,
    // Client secret key. Can only be hard-coded or stored as .env variable. Do not expose.
    clientSecret: EXAMPLE_CLIENT_SECRET,
    // This is the route in your auth server app where passport will call auth callback
    // and initiate code for token exchange
    callbackURL: "http://localhost:3333/auth/callback"
    // Tell passport to handle and validate state parameter
    state: true,
    // if the api doesn't provide you with user profile, only access token, then you can skip it
    skipUserProfile: true
  },
  // verify function
  (
    accessToken: string,
    refreshToken: string,
    profile: unknown,
    callback: VerifyCallback,
  ) => {
    if (accessToken || profile) {
      // here you can either have a profile or not
      // if not, validate an access token, e.g fetch some data from API
      // then if you store users, you can create or find such user in DB 
      // and finally call a callback and pass user as a second argument
      callback(null, user, { accessToken })
    } else {
      // handle error, pass it as first (error) or as third (info) argument
      // and false as a second argument (user)
      callback({ message: 'error' }, false)
      // or callback(null, false, { message: 'error' })
    }
  }
))

// Serialize and deserialize user
// you can use whole object or just user ID, for example
passport.serializeUser((user: object, done) => {
  done(null, user)
})

passport.deserializeUser((user: object, done) => {
  done(null, user)
})

// Listening section

And after the strategy is serialized, we can use the password.js authenticate function.

Step 6. Auth routes and Passport.js authenticate functions

The biggest advantage of Passport.js is that it uses the same function for auth (code request) and its callback (exchange for token). Passport parses a request's query or body (depending on the request method), and if the code field is present, then it initiates token exchange (callback). Otherwise, it will initiate the authorization code request.

Passport.js will build all the query parameters by itself. If you enable state in strategy initialization, then Passport.js will add this parameter automatically to your authorization code request. The query for this request will look like this:

  • response_type: coderedirect_uri: adds your callbackUrl from strategy initialization
  • client_id: adds your clientID from strategy initialization
  • state: if state is enabled, then creates it, stores in session, and after validates it in the callback.

For the token exchange request, the query will look like:

  • grant_type: authorization_code
  • code: obtained via callback request authorization code from the OAuth 2.0 provider
  • client_id: adds your clientID from strategy initialization
  • client_secret: adds your clientSecret from strategy initialization

When looking for ways on how to implement OAuth, please note that you can also pass state as an option field into the authenticate function. This custom state will be added to the request query. However, it will break state handling and validation by passport, so if you don’t have any specific limitations or validations for the state string, you should avoid it and allow Passport.js to do the job. Otherwise, you must create, store, and validate it yourself. And don’t forget to disable the state inside the strategy because Passport.js will restrict you from obtaining an access token.

You can override two functions for two requests if you need to add some specific query parameters. They take options from the authenticate function as input.

Typescript

OAuth2Strategy.prototype.authorizationParams = (options) => {
 return {}
}
OAuth2Strategy.prototype.tokenParams = (options) => {
 return {}
}

And if you need to add some additional options to the Passport.js authentication options, you should perform the module augmentation for the passport’s AuthenticateOptions interface.

Finally, let’s proceed with authentication routes.

Step 7. Authentication routes and requests.

Use passport.authenticate(), specifying the oauth2 strategy, to perform authentication requests. Let’s see the implementation for our Express.js application:

Typescript, main.ts

// Serialize and deserialize user
// you can use whole object or just user ID, for example
passport.serializeUser((user: object, done) => {
  done(null, user)
})

passport.deserializeUser((user: object, done) => {
  done(null, user)
})
// Routing section 
app.get('/auth, passport.authenticate('oauth2'))

app.get('/auth/callback', (req, res, next) => {
  passport.authenticate(
    'oauth2', 
    { 
      // you can specify some options here or use the callback and keep options empty
      // failureRedirect won't trigger on error, only if this message is passed in info
      failureMessage: true,
      failureRedirect: '/error',
      successMessage: true,
      successRedirect: '/success',
      keepSessionInfo: true
      // if you will add a callback, then all these redirects won't work
    },
    (err: any, user: object | false, info: Record<string, any>) => {
      if (user) {
        // authentication was successful, let's proceed with login
        // Passport.js adds req.logIn function
        // by default, without custom callback, it is performed by passport.
        req.logIn(user, { session: true, keepSessionInfo: true }, (error) => {
          if (error) {
            // login was not successful
            console.error('Error on user login, error:', error)
          } else {
            // successfully logged in, perform your actions here 
           // use access token from info and/or perform redirect
          }
        })
      } else {
        // No user data was obtained
        console.error(
          'Error on auth callback, error:',
          err ?? info?.['messages']?.join(' ') ?? 'Unknown error',
        )
        // handle error here, redirect or any other action
      }
    }
  )
})

// Listening section
const port = process.env.NX_APP_PORT || 3333
const server = app.listen(port, () => {
 console.log(`Listening at http://localhost:${port}/api`)
});
server.on('error', console.error);

And just like that, the finish line was reached. The last thing to do is to pass the user data or accessToken to your application from your server. For example, you can set a custom httpOnly cookie with an accessToken and add a proxy-request for getting user data from your provider.

Please note that no sensitive information should be exposed in the request's query.

Get inspired by our blog post about Java relevance in 2024.

Conclusion

In conclusion, implementing OAuth 2 with Express.js and Passport.js offers a robust authentication solution, benefitting from Passport.js's automatic handling of various complexities like query parameters and state checking. This abstraction allows developers to focus on application logic. Another significant advantage of Passport.js is its extensive support for 49 strategies for OAuth, providing the flexibility to implement multiple strategies within a single Express.js app.

However, it's important to note some downsides, especially in making customizations and dealing with errors. Customizing Passport.js for specific project needs can be tricky, requiring a deep understanding and potentially making it more challenging for developers, with a longer learning curve and setup time. Furthermore, the error handling mechanism in Passport.js is not as detailed, which may cause frustration during implementation and debugging.

Another drawback worth considering is that if you want to utilize the "state" feature, you shouldn't override it in authorization parameters. Doing so can result in an authentication error, adding an extra layer of complexity to the implementation process.

In conclusion, Passport.js remains a valuable choice for OAuth 2 implementations, offering efficiency and versatility with its array of strategies. Developers should carefully weigh these advantages against potential challenges, considering the trade-off between ease of use and customization based on their project requirements and priorities.

Further improvements

In this case study, a basic OAuth 2.0 authentication process using Express.js and Passport.js has been outlined. However, the authentication is not connected to a database, and there's no mechanism for refreshing tokens. To enhance this example:

  1. Database Integration: Establish a link between the authentication process and a database. This involves locating or creating a user based on data received from the OAuth 2.0 provider. Storing user information in a database offers a more flexible and scalable user management system.
  2. Token Refreshing: Improve security and user experience by implementing token refreshing. This means creating a way to get new access tokens when the old ones expire. Refreshing tokens are vital for a secure and uninterrupted user experience.

Implementing these changes will fortify the OAuth 2.0 setup, adhering to best practices for user management and keeping the application secure.

If you need help with the oauth2 protocol implementation on your project, don’t hesitate to contact our IT professionals to get some details!

We know how to make it faster, here’s why

Our estimations

Axon takes pride in offering cutting-edge solutions and services underpinned by agile project management methodologies. We recognize the paramount significance of precise estimations in meeting client expectations and project deadlines.

Our approach to estimations revolves around close collaboration with our clients. We understand that every project is unique, and client preferences play a crucial role in defining the scope and scale of software development initiatives. By actively engaging with our clients, we gain deep insights into their specific requirements, priorities, and budgetary constraints. Leave your contacts, and we will provide you with estimations in 24 hours. We know how to implement OAuth 2.0 faster and cheaper!

Our experience

At Axon, we pride ourselves on our extensive experience in software engineering and our commitment to staying at the forefront of industry best practices. One area where our expertise truly shines is in the seamless expertise on how to implement OAuth 2.0 into diverse applications.

Our seasoned team of software engineers has successfully implemented OAuth 2.0 across a spectrum of projects, ranging from small-scale applications to large enterprise systems. No two applications are alike, and we recognize the importance of tailoring OAuth 2.0 implementations to suit the unique requirements of each project. For example, when working on how to implement OAuth in Java, developers typically use a library or framework that supports OAuth, such as Spring Security or Apache Oltu. They integrate the chosen library into their Java application and follow the documentation or tutorials provided by the library to set up OAuth authentication for their specific use case.

Through careful analysis and in-depth consultations, we ensure that our solutions align with your application's specific needs while maintaining the highest standards of security.

Our team

Throughout the software engineering process, our team has demonstrated a well-established track record of collaboration and professionalism when working with our esteemed partners.

We possess a deep expertise on how to implement OAuth2 in Java. Our team's agility enables us to embrace change and tackle complex challenges with confidence. We approach each project with a flexible mindset, tailoring our methodologies to suit the unique requirements and goals of our clients. Through agile project management, we ensure that our solutions are scalable, maintainable, and adaptable to future needs.

Aspect What Axon Offers
Expertise 12 years of unmatched experience on the IT market
Tailored Solutions Customized solutions for your industry
Cost-Effective Excellence Maximizing your budget without compromising quality
Comprehensive Services End-to-end services for a smooth and cost-effective journey
Future-Proofing Building for future scalability and adaptability
Responsive Design Ensuring a consistent user experience across devices
Cross-Platform Development Expanding your reach with minimal cost
Client Success Stories Proven track record with 130+ successful projects and satisfied clients
Reach out to hello@axon.dev for a leading software solution!

Need estimation?

Are you ready to elevate your software development to the next level? Make estimation of your future product with our easy-to-use mobile calculator!

Let's work together to design an application that not only meets your budget but also propels your business to new heights!

Software development Team

[1]

Need estimation?

Leave your contacts and get clear and realistic estimations in the next 24 hours.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
coin image
Estimate Your Mobile App
Take a quick poll and get a clear price estimation

TRY NOW