commit
97496d90f3
5 changed files with 1674 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||||
|
keycloak.json |
||||
|
node_modules |
||||
|
|
||||
@ -0,0 +1,91 @@ |
|||||
|
# Keycloak NodeJS Adapter demo |
||||
|
|
||||
|
## Setup |
||||
|
|
||||
|
```sh |
||||
|
git clone https://github.com/nmasse-itix/keycloak-nodejs-demo.git |
||||
|
cd keycloak-nodejs-demo |
||||
|
npm install |
||||
|
``` |
||||
|
|
||||
|
## Demo scenario |
||||
|
|
||||
|
Install Red Hat SSO. |
||||
|
|
||||
|
Create a Realm named Red Hat. |
||||
|
|
||||
|
Start the Petstore Server. |
||||
|
|
||||
|
```sh |
||||
|
node index.js |
||||
|
``` |
||||
|
|
||||
|
Show some REST requests. |
||||
|
|
||||
|
```sh |
||||
|
http http://localhost:8080/pets/ |
||||
|
http http://localhost:8080/pets/1 |
||||
|
``` |
||||
|
|
||||
|
Create a client named "nodejs-client", **Bearer Only**. |
||||
|
|
||||
|
Download **keycloak.json** from the **Installation** tab (select **Keycloak OIDC JSON** from the dropdown list). |
||||
|
|
||||
|
Uncomment all lines in index.js and restart the Petstore server. |
||||
|
|
||||
|
Show that the Petstore server now requires authentication. |
||||
|
|
||||
|
```sh |
||||
|
http http://localhost:8080/pets/ |
||||
|
``` |
||||
|
|
||||
|
Create a **confidential** client named "rest-client", with only the **Direct Access Grants** flow enabled. |
||||
|
|
||||
|
Create a user **john** with password **secret**. |
||||
|
|
||||
|
Request a token for john. |
||||
|
|
||||
|
```sh |
||||
|
curl https://$SSO_HOSTNAME/auth/realms/redhat/protocol/openid-connect/token -XPOST -d client_id=rest-client -d client_secret=$CLIENT_SECRET -d grant_type=password -d username=john -d password=secret |
||||
|
``` |
||||
|
|
||||
|
Save it for later. |
||||
|
|
||||
|
```sh |
||||
|
TOKEN=$(curl https://$SSO_HOSTNAME/auth/realms/redhat/protocol/openid-connect/token -XPOST -d client_id=rest-client -d client_secret=$CLIENT_SECRET -d grant_type=password -d username=john -d password=secret -s |jq -r .access_token) |
||||
|
``` |
||||
|
|
||||
|
Call one of the REST endpoints. |
||||
|
|
||||
|
```sh |
||||
|
http http://localhost:8080/pets/ "Authorization:Bearer $TOKEN" |
||||
|
``` |
||||
|
|
||||
|
Now edit index.js and update all REST endpoint to check for user roles, either **read** or **write**. Like this: |
||||
|
|
||||
|
```js |
||||
|
router.get("/pets/:id", keycloak.protect("read"), function(req,res){ |
||||
|
``` |
||||
|
|
||||
|
Restart the petstore server and get a new token. |
||||
|
|
||||
|
Show that now, calls are rejected. |
||||
|
|
||||
|
```sh |
||||
|
http http://localhost:8080/pets/ "Authorization:Bearer $TOKEN" |
||||
|
``` |
||||
|
|
||||
|
Give the **read** role to john, get a new token and show that you can query the Read REST endpoints. |
||||
|
|
||||
|
```sh |
||||
|
http http://localhost:8080/pets/ "Authorization:Bearer $TOKEN" |
||||
|
``` |
||||
|
|
||||
|
Write calls a forbidden. |
||||
|
|
||||
|
```sh |
||||
|
http DELETE http://localhost:8080/pets/1 "Authorization:Bearer $TOKEN" |
||||
|
``` |
||||
|
|
||||
|
Give the **write** role to john, get a new token and show that you can query the Write REST endpoints. |
||||
|
|
||||
@ -0,0 +1,147 @@ |
|||||
|
const express = require('express'); |
||||
|
const cors = require('cors') |
||||
|
const _ = require("underscore"); |
||||
|
const bodyParser = require('body-parser'); |
||||
|
const util = require('util'); |
||||
|
//const session = require('express-session');
|
||||
|
//const Keycloak = require('keycloak-connect');
|
||||
|
|
||||
|
// CORS Setup
|
||||
|
var corsConfig = { |
||||
|
"origin": true, |
||||
|
"credentials": true |
||||
|
}; |
||||
|
|
||||
|
// ExpressJS Setup
|
||||
|
const app = express(); |
||||
|
app.use(cors(corsConfig)); // Handle CORS requests
|
||||
|
var router = express.Router(); |
||||
|
var port = 8080; |
||||
|
|
||||
|
var pets = { "1": {"id":1,"name":"Eclair","tag":"cat"}, |
||||
|
"2": {"id":2,"name":"Cannelle","tag":"cat"} }; |
||||
|
var counter = 3; |
||||
|
|
||||
|
// Enable sessions in ExpressJS
|
||||
|
/* |
||||
|
const memoryStore = new session.MemoryStore(); |
||||
|
app.use(session({ |
||||
|
store: memoryStore, |
||||
|
secret: 's3cr3t!', |
||||
|
resave: false, |
||||
|
saveUninitialized: true, |
||||
|
cookie: { secure: false } |
||||
|
})); |
||||
|
*/ |
||||
|
|
||||
|
// Enable the Keycloak adapter
|
||||
|
//const keycloak = new Keycloak({ store: memoryStore });
|
||||
|
//app.use(keycloak.middleware());
|
||||
|
|
||||
|
// Handle CORS pre-flight request
|
||||
|
app.options('*', cors(corsConfig)); |
||||
|
|
||||
|
// Log every request
|
||||
|
router.use(function (req,res,next) { |
||||
|
next(); |
||||
|
console.log("%s %s => %d", req.method, req.originalUrl, res.statusCode); |
||||
|
}); |
||||
|
|
||||
|
// All requests must be authenticated
|
||||
|
//router.use(keycloak.protect());
|
||||
|
|
||||
|
// Get all the pets
|
||||
|
router.get("/pets", function(req,res){ |
||||
|
success(res, 200, _.values(pets)); |
||||
|
}); |
||||
|
|
||||
|
// Get a pet
|
||||
|
router.get("/pets/:id", function(req,res){ |
||||
|
var id = req.params.id; |
||||
|
if (! (id in pets)) { |
||||
|
return error(res, 404, util.format("No such pet with id '%s' !", id)); |
||||
|
} |
||||
|
|
||||
|
success(res, 200, pets[id]); |
||||
|
}); |
||||
|
|
||||
|
// Create a pet
|
||||
|
router.post("/pets", function(req,res){ |
||||
|
var pet = req.body; |
||||
|
if (pet == null) { |
||||
|
return error(res, 400, "No body sent !"); |
||||
|
} |
||||
|
|
||||
|
pet.id = counter++; |
||||
|
pets[pet.id] = pet; |
||||
|
success(res, 201, pet); |
||||
|
}); |
||||
|
|
||||
|
// Delete a pet
|
||||
|
router.delete("/pets/:id", function(req,res){ |
||||
|
var id = req.params.id; |
||||
|
if (! (id in pets)) { |
||||
|
return error(res, 404, util.format("No such pet with id '%s' !", id)); |
||||
|
} |
||||
|
|
||||
|
var pet = pets[id]; |
||||
|
delete pets[id]; |
||||
|
success(res, 200, pet); |
||||
|
}); |
||||
|
|
||||
|
// Update a pet
|
||||
|
router.put("/pets/:id", function(req,res){ |
||||
|
var pet = req.body; |
||||
|
if (pet == null) { |
||||
|
return error(res, 400, "No body sent !"); |
||||
|
} |
||||
|
|
||||
|
var id = req.params.id; |
||||
|
if (! (id in pets)) { |
||||
|
return error(res, 404, util.format("No such pet with id '%s' !", id)); |
||||
|
} |
||||
|
|
||||
|
if (pet.id != id) { |
||||
|
return error(res, 400, util.format("The id cannot be updated: '%s' vs '%s'", pet.id, id)); |
||||
|
} |
||||
|
|
||||
|
pets[id] = pet; |
||||
|
success(res, 200, pet); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
//
|
||||
|
// Please find below the plumbing code
|
||||
|
//
|
||||
|
|
||||
|
// Register the JSON Parser for POST and PUT requests
|
||||
|
app.use(bodyParser.json()); |
||||
|
|
||||
|
// Register the router
|
||||
|
app.use("/",router); |
||||
|
|
||||
|
// 404 Handler (Not Found)
|
||||
|
app.use("*",function(req,res){ |
||||
|
error(res, 404, "Not found"); |
||||
|
}); |
||||
|
|
||||
|
// Start the HTTP Server
|
||||
|
var server = app.listen(port,function(){ |
||||
|
console.log("API Mockup listening at port %d", port); |
||||
|
}); |
||||
|
|
||||
|
function error(res, code, message) { |
||||
|
var response = { |
||||
|
status: code, |
||||
|
message: message |
||||
|
}; |
||||
|
return res.status(code) |
||||
|
.type("application/json") |
||||
|
.send(JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
function success(res, code, response) { |
||||
|
return res.status(code) |
||||
|
.type("application/json") |
||||
|
.send(JSON.stringify(response)); |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,22 @@ |
|||||
|
{ |
||||
|
"name": "keycloak-nodejs-demo", |
||||
|
"version": "1.0.0", |
||||
|
"description": "A demo of the Keycloak NodeJS Adapter", |
||||
|
"repository": "https://github.com/nmasse-itix/keycloak-nodejs-demo.git", |
||||
|
"main": "index.js", |
||||
|
"dependencies": { |
||||
|
"body-parser": "^1.19.0", |
||||
|
"cors": "^2.8.5", |
||||
|
"express": "^4.17.1", |
||||
|
"express-session": "^1.17.1", |
||||
|
"keycloak-connect": "^12.0.4", |
||||
|
"underscore": "^1.12.1", |
||||
|
"util": "^0.12.3" |
||||
|
}, |
||||
|
"devDependencies": {}, |
||||
|
"scripts": { |
||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||
|
}, |
||||
|
"author": "Nicolas Massé", |
||||
|
"license": "MIT" |
||||
|
} |
||||
Loading…
Reference in new issue