Skip to content

Instantly share code, notes, and snippets.

@thomd
Last active January 14, 2025 09:52
Show Gist options
  • Save thomd/7074bc8c90d552c400e124d556e1d675 to your computer and use it in GitHub Desktop.
Save thomd/7074bc8c90d552c400e124d556e1d675 to your computer and use it in GitHub Desktop.
OAuth Client Credentials Flow Conceptional Example
// 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