Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security / Identity

Secure Authentication for Web Applications: Avoiding Password Storage to Mitigate Cybersecurity Risks

3.43/5 (4 votes)
10 Apr 2023MIT9 min read 4.7K  
The Importance of Secure Authentication in Web Applications and the Role of Identity Providers
Securing user data is paramount, especially for web applications that require authentication. However, handling sensitive data such as usernames and passwords can be a challenge, as they are often targeted by hackers. To mitigate this risk, the author suggests using an identity provider such as Google and Facebook to handle authentication instead of storing sensitive data themselves. While this solution is not perfect, it provides a simpler and safer approach to authentication. In this article, the author discusses the advantages of using an identity provider, the limitations of this solution, and how to mitigate potential issues. It also includes a practical example (code) that demonstrates how to use Spotify authentication with Next.js.

Introduction

This article discusses how an identity provider solution was used to address a common security challenge in a personal project. It outlines the benefits and limitations of this approach, and provides a code example demonstrating how to implement Spotify authentication with Next.js. The example covers setting up an authentication flow using the next-auth library, as well as adding authentication routes.

Context

My friends and I started a garage band. We were very committed, had rehearsals, and lots of fun with busking performances. At first, everything was great, we enjoyed improvising different music styles and creating mashups. However, as we progressed, we encountered some issues that needed to be addressed.

One of the main issues we faced was differences in song lyrics and chords. Sometimes, each of us had a different version of the lyrics or chords, especially for songs with live or acoustic versions. In addition, we had trouble sharing and updating playlists, which had to be shared again with the entire group every time we made changes.

To overcome these problems, the IT guy here decided to create a software that could help our band and others like us around the world. Yet one of the biggest challenges was ensuring secure authentication for users. While most web applications require authentication, I knew that user passwords needed to be handled with utmost care, as they are a common target for hackers.

The Problem

Most web applications, including my software, require a secure authentication system to ensure user data is protected. While this is a fundamental concept, it's important to recognize that there are different levels of data security. Generally, two main groups of data require varying levels of security in my opinion:

  1. Usernames and passwords
  2. Other types of data (“all the rest”)

It's important to note that this differentiation may not apply to everything. For example, when I worked at a bank, almost all data was considered confidential and required encryption. Therefore, there were multiple levels of security within a simple system. However, in the context of my songs software, I will focus on the two groups mentioned above.

The reason usernames and passwords require greater attention is because they are a common target for hackers. Once hackers gain access to an application using stolen login credentials, they can access and manipulate sensitive data, which can lead to severe consequences.

To put it differently, I am confident in my ability to secure the "all the rest" data because it is unlikely to contain sensitive content. However, the responsibility of storing user passwords in my database is much greater, as 70% of users tend to reuse passwords across multiple accounts (according to a study by Spycloud in 2021). As such, I do not want to be held responsible for a potential data breach that could compromise usernames and passwords, which can be used to access other applications.

Consider the following illustration:

robber and front desk

A robber threatens a hotel receptionist, demanding that he hands over the keys to all the rooms. With the keys in hand, the robber proceeds to enter some of the rooms and steal anything of value.

As you may have deduced, a hacker is like the robber. If they exploit a security vulnerability in my (or your) application, they can gain access to sensitive data and potentially breach other applications that share the same login credentials.

You already know that many users tend to reuse the same password across multiple platforms, which can make it easier for hackers to gain unauthorized access to their accounts.

The Solution

Please consider the following quote and see if you share the same insight as me:

“You cannot lose what you never had” — Izaak Walton

It may seem a bit odd to quote an old English writer when discussing modern technology, but his words offer a solution that may be applicable in this context. Instead of storing sensitive data such as logins and passwords, what if we simply did not store them at all?

Coming back to our illustrative example, a better security model would be this:

confused robber and front desk

A robber threatens a hotel receptionist, demanding that he gives the keys to all the rooms. However, the receptionist cannot comply, as he does not have access to the keys. Instead, he explains to the robber that the hotel has a different system, where each guest is responsible for storing their own key in a much more secure place. The robber is advised to try his luck there instead.

That is exactly what we want: to not be responsible for storing and potentially leaking any user passwords. If any problems arise, it will not be our fault - after all, "You cannot lose what you never had".

In terms of software, what we want is an identity provider. There are many out there. The most common ones are:

  • Google
  • Facebook
  • Apple
  • Microsoft

You probably have noticed that many web applications provide the option to log in using at least one of the identity providers mentioned above. Considering the profile of my application's users, I would say that most of them have a Spotify account, whether it is a free or premium one. After all, which person involved with music (amateur player or not) does not have a Spotify account?

Therefore, Spotify is the chosen identity provider for my software. In rare cases where a user does not have a Spotify account, the fallback solution would be to use Google or Facebook providers. However, for now, I want to keep it simple and prefer to prompt users to create a Spotify account if they still do not have one. As a result, it will avoid further maintenance and possible issues.

Additionally, I intend to use the Spotify API in the future to add cool features related to music.

In summary, the simplest and safest approach to incorporating authentication is to avoid developing it yourself. Google and Facebook have greater means to safeguard your data than we do.

Limitations of the Solution

Unfortunately, this solution is not perfect as there are a couple of things that might cause problems because we are depending on third-party services. The main ones are:

  1. the authentication service is down
  2. a security breach in third-party services

How Can We Mitigate Such Issues?

  1. The authentication service is off: It is unlikely that multiple authentication providers will experience issues simultaneously (e.g., Google and Spotify not working). Therefore, the first step is to prompt the user to log in with the provider that is still operational. However, it is crucial to link the accounts ahead of time to prevent such issues.
  2. Security breach in third parties: While a security breach in a third-party provider is not our fault, our software data becomes vulnerable. The provider may shut down its service to prevent further issues, which may prevent attackers from logging into our software. However, we can be proactive and disable the problematic authentication provider. There are various ways to implement this. You may want to retain control in the cloud server or application container, or you may provide an admin user with the ability to easily disable the provider as a function in your application.

Implementing the Solution

One of my main goals in implementing this solution was to learn a new technology or framework that could help speed up the development process, particularly when it comes to user authentication. After some research, I decided to use Next.js (React) from Vercel.

To provide a clearer understanding of how the solution was implemented, I will include some code snippets in this article.

To begin, let's add the next-auth dependency by running the following command:

JavaScript
npm install next-auth      

After installing next-auth, the package.json file will include the latest stable version, which for my case was "next-auth": "^4.19.2".

Now create the API route. Navigate to the pages/api/auth folder and create a file named [...nextauth].js (yes, including the square brackets). By following this folder structure pattern and file naming convention, NextAuth will automatically generate and manage various components for you, reducing boilerplate code and complexity.

The following code represents the core implementation of our identity provider.

JavaScript
import NextAuth from 'next-auth';
import SpotifyProvider from 'next-auth/providers/spotify';

export default NextAuth({
   providers: [
      SpotifyProvider({
         clientId: process.env.SPOTIFY_CLIENT_ID,
         clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
      }),
   ],
});      

As you can see, everything necessary is imported from a single package: next-auth.

JavaScript
import NextAuth from 'next-auth';
import SpotifyProvider from 'next-auth/providers/spotify';      

Even the Spotify provider is imported from there. By the way, Spotify uses the OAuth 2.0 authorization network. We also need to provide some additional data to link the provider to our application, which can be seen in:

JavaScript
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,      

To obtain the client ID and secret for the Spotify provider, you'll need to step outside of the code and perform some configuration on the Spotify website. Here's an overview of the steps you'll need to follow:

  1. Sign up for a Spotify developer account if you don't already have one. You can do this on the Spotify Developer Dashboard website at https://developer.spotify.com/dashboard.
  2. Create an app on the dashboard, which you can name after your software.
  3. Configure the app for authorization and user data access.
  4. Retrieve the client ID and secret from the app configuration.

Now that you have obtained the client ID and secret, you can add them to the .env.local file in your project. This file should be located in the root directory where package.json and other files are located.

JavaScript
SPOTIFY_CLIENT_ID='xxxxxxxxxxxxxxxxxxxxx'
SPOTIFY_CLIENT_SECRET='xxxxxxxxxxxxxxxxxxxxx'      

If you are using Vercel, it is a good practice to configure the environment variables now:

Vercel configuration

To make the session data available for the entire application, add the SessionProvider to pages/_app.jsx. Your code should look something like this:

JavaScript
import { SessionProvider } from 'next-auth/react';

export default function MyApp = ({ Component, pageProps: 
                                 { session, ...pageProps } }) {
   return (
      <SessionProvider session={session}>
            <Component {...pageProps} />
      </SessionProvider>
   );
}

The useSession React Hook allows you to check if the user is authenticated and retrieve additional information such as their name, email, and image. The signIn and signOut functions provided by next-auth make the process of signing in and out simple and straightforward.

JavaScript
import { useSession, signIn, signOut } from "next-auth/react"

const { data: session, status } = useSession();

if (session) {
   {session.user.name}
   {session.user.email}
   {session.user.image}
  <button onClick={() => signOut()}>Sign out</button>
} else {
  <button onClick={() => signIn()}>Sign in</button>
}     

That is all you need to have your identity provider working.

For my application, I went further and created an auth context and provider to check if the user who just authenticated themselves in Spotify is an existing user of my app. If so, I retrieve specific data from that user and redirect them to the main page where their songs are listed. If not, the user is invited to create an account.

Other features I added included specific pages for user sign-up and error handling. You can create these pages and configure them in the [...nextauth].js file. For example:

JavaScript
pages: {
   signIn: '/auth/new-user',
   error: '/auth/error',
},      

Conclusion

The development of the application was a success, as my friends enjoyed using it and all of the issues mentioned in the beginning of this article were successfully resolved. However, this essay specifically addresses the common challenge of risks and liabilities involved in handling sensitive user data, such as usernames and passwords.

In this article, a solution to this issue was proposed by utilizing identity providers like Spotify, Google, and Facebook, and implementing NextAuth.js. By trusting in established services and simplifying the authentication process, it was possible to create a secure and efficient application, and perhaps most importantly, by not storing any usernames or passwords on our end, we have minimized the potential consequences of any future data leaks.

If, unfortunately, a data breach were to occur someday, I would be like a hotel receptionist who doesn't hold the keys: “Sorry, I don't have them!”

History

  • 10th April, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License