Last active
January 14, 2025 09:52
-
-
Save thomd/7074bc8c90d552c400e124d556e1d675 to your computer and use it in GitHub Desktop.
OAuth Client Credentials Flow Conceptional Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// npm i express body-parser axios jsonwebtoken | |
// node oauth2-client-credentials.js | |
const express = require('express') | |
const bodyParser = require('body-parser') | |
const axios = require('axios') | |
const jwt = require('jsonwebtoken') | |
const PORT_AUTH_SERVER = 4000 | |
const PORT_RESOURCE_SERVER = 5000 | |
const PORT_CLIENT = 3000 | |
const SECRET_KEY = 'your_jwt_secret_key' | |
const logger = function (req, res, next) { | |
const h = (r, n) => r.rawHeaders.filter((e, i, a) => a[i - 1] == n)[0] | |
console.log('\n->', req.method, h(req, 'Host') + req.url) | |
console.log('Authorization:', h(req, 'Authorization')) | |
console.log('Content-Type:', h(req, 'Content-Type')) | |
console.log('Body:', req.body) | |
const send = res.send | |
res.send = function (body) { | |
console.log('\n<-', h(res.req, 'Host')) | |
console.log(body) | |
send.call(this, body) | |
} | |
next() | |
} | |
// ==================== Client ==================== | |
const client = express() | |
client.use(logger) | |
client.get('/access-resource', async (req, res) => { | |
try { | |
// Step 1: Obtain access token | |
const tokenResponse = await axios.post( | |
`http://localhost:${PORT_AUTH_SERVER}/token`, | |
new URLSearchParams({ | |
grant_type: 'client_credentials', | |
client_id: 'my-client-id', | |
client_secret: 'my-client-secret', | |
}), | |
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } | |
) | |
const accessToken = tokenResponse.data.access_token | |
// Step 2: Access protected resource | |
const resourceResponse = await axios.get( | |
`http://localhost:${PORT_RESOURCE_SERVER}/protected-data`, | |
{ headers: { Authorization: `Bearer ${accessToken}` } } | |
) | |
res.json({ | |
//access_token: accessToken, | |
protected_data: resourceResponse.data, | |
}) | |
} catch (error) { | |
res.status(500).json({ error: error.message }) | |
} | |
}) | |
client.listen(PORT_CLIENT, () => { | |
console.log(`Client running on http://localhost:${PORT_CLIENT}`) | |
}) | |
// ==================== Authorization Server ==================== | |
const authServer = express() | |
authServer.use(bodyParser.urlencoded({ extended: true })) | |
authServer.use(bodyParser.json()) | |
authServer.use(logger) | |
// Mock database for clients | |
const clients = { | |
'my-client-id': { | |
clientSecret: 'my-client-secret', | |
}, | |
} | |
// Endpoint to issue access tokens | |
authServer.post('/token', (req, res) => { | |
const { grant_type, client_id, client_secret } = req.body | |
if (grant_type !== 'client_credentials') { | |
return res.status(400).json({ error: 'unsupported_grant_type' }) | |
} | |
const client = clients[client_id] | |
if (!client || client.clientSecret !== client_secret) { | |
return res.status(401).json({ error: 'invalid_client' }) | |
} | |
// Issue a simple token (not a real JWT for simplicity) | |
const accessToken = jwt.sign({ client_id }, SECRET_KEY, { expiresIn: '1h' }) | |
return res.json({ | |
access_token: accessToken, | |
}) | |
}) | |
authServer.listen(PORT_AUTH_SERVER, () => { | |
console.log(`Authorization Server running on http://localhost:${PORT_AUTH_SERVER}`) | |
}) | |
// ==================== Resource Server ==================== | |
const resourceServer = express() | |
resourceServer.use(logger) | |
// Middleware to validate JWT access token | |
resourceServer.use((req, res, next) => { | |
const authHeader = req.headers['authorization'] | |
if (!authHeader || !authHeader.startsWith('Bearer ')) { | |
return res.status(401).json({ error: 'missing_or_invalid_token' }) | |
} | |
const token = authHeader.split(' ')[1] | |
// Validate JWT token | |
try { | |
const payload = jwt.verify(token, SECRET_KEY) | |
req.client_id = payload.client_id // Attach client ID to request for further use if needed | |
next() | |
} catch (error) { | |
return res.status(401).json({ error: 'invalid_token', message: error.message }) | |
} | |
}) | |
// Protected resource endpoint | |
resourceServer.get('/protected-data', (req, res) => { | |
res.json({ data: 'Here is the protected data' }) | |
}) | |
resourceServer.listen(PORT_RESOURCE_SERVER, () => { | |
console.log(`Resource Server running on http://localhost:${PORT_RESOURCE_SERVER}`) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment