The client credentials workflow is described in RFC 6749, section 4.4:
+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+
In this setup the authorization server does also contain the consumable resources. Therefore, the setup looks something like this:
+----------+ +--------------+
| | | |
| Consumer |>--(A)- Client Authentication --->| Provider |
| | | |
| |<--(B)---- Access Token ---------<| |
| | | |
| |>--(C)---- Access resource ---------> [Resource] |
| | | | |
| |<--(D)----Resource response -----<--------< |
+----------+ +--------------+
- @node-oauth/express-oauth-server (uses @node-oauth/oauth2-server)
- express
- body-parser
- dotenv
- node-fetch
- dotenv
If you haven't already cloned this repository, then clone it via
$ git clone https://github.com/node-oauth/node-oauth2-server-examples.git
$ cd server2server
Since we have two servers you need to install dependencies in both. First, start with the provider:
$ cd provider
$ npm install
$ npm run start
The provider runs on http://localhost:8080
$ cd ../consumer
$ npm install
$ npm run start
The consumer will now make several requests. Note, that some of them are expected to fail. The overall output should look like so:
[Consumer]: get /public (public)
[Consumer]: => response: 200 OK moo
[Consumer]: get /read-resource (not authenticated)
[Consumer]: => response: 401 Unauthorized
[Consumer]: post /token (bad credentials)
[Consumer]: => response: 401 Unauthorized {"error":"invalid_client","error_description":"Invalid client: client is invalid"}
[Consumer]: post /token (bad credentials)
[Consumer]: => response: 200 OK {"access_token":"45f81685482d6e1337b99ddb8726b7c04355b3d427b1401cf08e5c3bea013a38","token_type":"Bearer","expires_in":3600,"scope":true}
[Consumer]: authorization token successfully retrieved!
[Consumer]: get /read-resource (authenticated, resource is not yet defined)
[Consumer]: => response: 200 OK {"resource":null}
[Consumer]: post /write-resource (authentication failed)
[Consumer]: => response: 401 Unauthorized {"error":"invalid_token","error_description":"Invalid token: access token is invalid"}
[Consumer]: post /write-resource (Invalid token)
[Consumer]: => response: 200 OK {"message":"resource created"}
[Consumer]: get /read-resource (authenticated, resource is now)
[Consumer]: => response: 200 OK {"resource":"foo-bar-moo"}
If you take a look at the provider/index.js
file, you will see a mix of public and private routes.
app.get('/protected-route', oauth.authenticate(), function (req, res) {
res.send({ resource: internal.resource });
})
If the authenticated middleware fails, there will be no next()
call into your provided handler function.
The authentication will fail, if no access token is provided.
In order to receive an access token, the client needs to make a call to the public /token
endpoint, first:
const tokenBodyParams = new URLSearchParams();
tokenBodyParams.append('grant_type', 'client_credentials');
tokenBodyParams.append('scope', 'read');
const response = await fetch('http://localhost:8080/token', {
method: 'post',
body: tokenBodyParams,
headers: {
'content-type': 'application/x-www-form-urlencoded',
'authorization': 'Basic ' + Buffer.from(`${client.id}:${client.secret}`).toString('base64'),
}
})
const token = await response.json()
const accessToken = token.access_token
const tokenType = token.token_type
With this token, the client can make authenticated requests to the protected endpoints:
const response = await fetch('http://localhost:8080/read-resource', {
method: 'get',
headers: {
'authorization': `${tokenType} ${accessToken}`
}
})
const resource = await response.json()
Since there are no refresh token involved, the requests may fail, due to expired token. It's up to the client to re-request a new token.