Securing RESTful APIs with OAuth 2.0
Introduction
OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service. It works by delegating user authentication to the service that hosts the user account and authorizing third-party applications to access the user account. This guide covers the basics of OAuth 2.0 and how to implement it to secure RESTful APIs.
Why Use OAuth 2.0?
OAuth 2.0 provides several benefits for securing APIs:
- Delegated access: Allows third-party applications to access APIs on behalf of a user.
- Granular permissions: Grants specific permissions to applications, ensuring they only access what they need.
- Improved security: Reduces the need for applications to handle user credentials directly.
- Standardization: Provides a standardized way to handle authorization.
OAuth 2.0 Roles
OAuth 2.0 defines four roles:
- Resource Owner: The user who authorizes an application to access their account.
- Client: The application requesting access to the resource owner's account.
- Resource Server: The server hosting the protected resources, capable of accepting and responding to protected resource requests.
- Authorization Server: The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization.
OAuth 2.0 Grant Types
OAuth 2.0 defines several grant types for different use cases:
- Authorization Code: Used for server-side applications.
- Implicit: Used for client-side applications (e.g., single-page apps).
- Resource Owner Password Credentials: Used for highly trusted applications.
- Client Credentials: Used for machine-to-machine communication.
Implementing OAuth 2.0
1. Setting Up the Authorization Server
The authorization server is responsible for authenticating the user and issuing access tokens. In this example, we'll use a library called OAuth2orize for a Node.js implementation.
Step 1: Install Dependencies
# Install dependencies
npm install express body-parser oauth2orize passport passport-http bcryptjs jsonwebtoken
Step 2: Create the Authorization Server
const express = require('express');
const bodyParser = require('body-parser');
const oauth2orize = require('oauth2orize');
const passport = require('passport');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
const server = oauth2orize.createServer();
// Dummy user store
const users = [
{ id: 1, username: 'user1', password: bcrypt.hashSync('password1', 8) },
];
// Dummy client store
const clients = [
{ id: 'client1', clientId: 'client1', clientSecret: 'secret1', redirectUris: ['http://localhost:3000/callback'] },
];
// Dummy authorization code store
let authCodes = [];
// Dummy token store
let tokens = [];
// Grant authorization code
server.grant(oauth2orize.grant.code((client, redirectUri, user, ares, done) => {
const code = jwt.sign({ clientId: client.clientId, userId: user.id }, 'secret', { expiresIn: '10m' });
authCodes.push({ code, clientId: client.clientId, redirectUri, userId: user.id });
done(null, code);
}));
// Exchange authorization code for access token
server.exchange(oauth2orize.exchange.code((client, code, redirectUri, done) => {
const authCode = authCodes.find(ac => ac.code === code);
if (!authCode || authCode.clientId !== client.clientId || authCode.redirectUri !== redirectUri) {
return done(null, false);
}
const token = jwt.sign({ clientId: client.clientId, userId: authCode.userId }, 'secret', { expiresIn: '1h' });
tokens.push({ token, clientId: client.clientId, userId: authCode.userId });
done(null, token);
}));
app.post('/token', passport.authenticate(['oauth2-client-password'], { session: false }), server.token(), server.errorHandler());
app.listen(4000, () => {
console.log('Authorization server is running on port 4000');
});
2. Setting Up the Resource Server
The resource server hosts the protected resources and verifies access tokens.
Step 1: Install Dependencies
# Install dependencies
npm install express body-parser jsonwebtoken
Step 2: Create the Resource Server
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const app = express();
app.use(bodyParser.json());
const users = [
{ id: 1, username: 'user1', email: 'user1@example.com' },
];
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, 'secret', (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
app.get('/api/users', authenticateToken, (req, res) => {
res.json(users);
});
app.listen(3000, () => {
console.log('Resource server is running on port 3000');
});
Using OAuth 2.0 with Your API
Once you have set up the authorization and resource servers, your clients can obtain access tokens and use them to access protected resources.
Example: Client Request for Access Token
POST /token
Host: localhost:4000
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTH_CODE&redirect_uri=http://localhost:3000/callback&client_id=client1&client_secret=secret1
Example: Client Request with Access Token
GET /api/users
Host: localhost:3000
Authorization: Bearer ACCESS_TOKEN
Conclusion
OAuth 2.0 is a robust framework for securing RESTful APIs by allowing third-party applications to access resources on behalf of users. By implementing OAuth 2.0, you can ensure secure and controlled access to your APIs, providing a better and safer user experience.