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