Get started with 33% off your first certification using code: 33OFFNEW

How to authenticate your API, various methods

5 min read
Published on 20th April 2023

API authentication is an essential aspect of securing your applications and ensuring that only authorized users can access your resources. In this article, we will explore various API authentication methods, provide server-side code examples for each, and discuss their respective pros and cons. By understanding the different options, you can make an informed decision about the best authentication method for your specific use case.

The code examples in this article are all in JavaScript, using Node.js and Express, but the same techniques can easily be used in other frameworks such as PHP and Laravel. If you're comfortable with the content in this article already and know your way around building basic APIs in JavaScript then you might be interested in our JavaScript fundamentals certification.

If you're building out an API for your application you may also be interested in our article on how to version your API, which covers different methods that allow you to introduce breaking changes.

Some of the code examples in this article use third party libraries to help with the heavy lifting. You may want to do some research into these libraries should you choose that authentication method, and understand their pros and cons. We've used the libraries in order to keep the implementation code simple to understand, without clogging it up with implementing the underlying tech and protocols.

Option 1: Basic Authentication

Basic Authentication is a simple and widely supported authentication method. It involves sending the user's credentials (username and password) as a base64-encoded string in the request's Authorization header.

Pros:

  • Easy to implement
  • Widely supported by clients and servers

Cons:

  • Credentials are sent with each request
  • Not suitable for high-security applications

Server-side Code Example (Node.js and Express):

const express = require('express');
const app = express();
const port = 3000;

app.use((req, res, next) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).send('Unauthorized');
  }

  const [type, credentials] = authHeader.split(' ');
  if (type.toLowerCase() !== 'basic') {
    return res.status(401).send('Unauthorized');
  }

  const [username, password] = Buffer.from(credentials, 'base64').toString().split(':');
  
  // Replace this with your own authentication logic
  if (username === 'your-username' && password === 'your-password') {
    return next();
  }

  res.status(401).send('Unauthorized');
});

app.get('/api/data', (req, res) => {
  res.send('Hello, authenticated user!');
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

Option 2: API Keys

API keys are unique identifiers that are issued by the API provider to authenticate clients. They are typically passed as a query parameter or in the request's header.

Pros:

  • Easy to implement and manage
  • Provides some level of access control

Cons:

  • Can be easily leaked or misused
  • Less secure than other authentication methods

Server-side Code Example (Node.js and Express):

const express = require('express');
const app = express();
const port = 3000;

const apiKeyMiddleware = (req, res, next) => {
  const apiKey = req.headers['x-api-key'] || req.query.api_key;

  // Replace this with your own API key validation logic
  if (apiKey && apiKey === 'your-api-key') {
    return next();
  }

  res.status(401).send('Unauthorized');
};

app.use(apiKeyMiddleware);

app.get('/api/data', (req, res) => {
  res.send('Hello, authenticated user!');
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

Option 3: JSON Web Tokens (JWT)

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims between two parties. They are often used for authentication and authorization purposes. JWTs consist of three parts: a header, a payload, and a signature.

Pros:

  • Stateless and scalable
  • Can include additional claims (user roles, permissions, etc.)
  • Widely adopted and supported

Cons:

  • Vulnerable to token theft
  • Token revocation can be challenging

Server-side Code Example (Node.js, Express, and jsonwebtoken library):

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const port = 3000;

const secretKey = 'your-secret-key';

const jwtMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader) {
    return res.status(401).send('Unauthorized');
  }

  const [type, token] = authHeader.split(' ');

  if (type.toLowerCase() !== 'bearer') {
    return res.status(401).send('Unauthorized');
  }

  try {
    const decoded = jwt.verify(token, secretKey);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).send('Unauthorized');
  }
};

app.use(jwtMiddleware);

app.get('/api/data', (req, res) => {
  res.send(`Hello, authenticated user with ID: ${req.user.id}!`);
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

Option 4: OAuth 2.0

OAuth 2.0 is an authorization framework that enables a third-party application to obtain limited access to resources on behalf of a user. It is widely used for API authentication and can support various grant types, such as authorization code, implicit, password, and client credentials.

Pros:

  • Delegates authentication to a trusted third party
  • Provides granular access control
  • Widely adopted and supported

Cons:

  • More complex to implement
  • Requires additional setup (authorization server, client registration, etc.)

Server-side Code Example (Node.js, Express, and Passport with OAuth2Strategy):

const express = require('express');
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2');
const app = express();
const port = 3000;

passport.use(new OAuth2Strategy({
  authorizationURL: 'https://authorization-server.example.com/oauth/authorize',
  tokenURL: 'https://authorization-server.example.com/oauth/token',
  clientID: 'your-client-id',
  clientSecret: 'your-client-secret',
  callbackURL: 'http://localhost:3000/auth/callback'
},
(accessToken, refreshToken, profile, done) => {
  // Replace this with your own user lookup and authentication logic
  const user = {
    id: 1,
    name: 'Authenticated User'
  };
  done(null, user);
}));

app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((user, done) => {
  done(null, user);
});

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

app.get('/auth', passport.authenticate('oauth2'));
app.get('/auth/callback', passport.authenticate('oauth2', { failureRedirect: '/auth' }), (req, res) => {
  res.redirect('/api/data');
});

app.get('/api/data', (req, res) => {
  if (!req.user) {
    return res.status(401).send('Unauthorized');
  }
  res.send(`Hello, authenticated user: ${req.user.name}!`);
});

app.listen(port, () => {
  console.log(`Server listening at http://localhost:${port}`);
});

In conclusion, each API authentication method has its pros and cons. The choice of method will depend on your specific use case, security requirements, and the level of granularity you need for access control. By understanding the differences between these options, you can select the most suitable authentication method for your API.