I'm trying to get user info from keycloak with keycloak.protect() middleware, but always get 403 "Access denied".
Keycloak version 24.0.2
I did my own realm and client
My keycloak.json
{
"realm": "realm",
"auth-server-url": "http://localhost:8080/",
"bearerOnly": true,
"ssl-required": "external",
"resource": "tender",
"verify-token-audience": true,
"credentials": {
"secret": "some secret"
},
"use-resource-role-mappings": true,
"confidential-port": 0,
"policy-enforcer": {
"credentials": {}
}
}
index.js
const app: Express = express();
app.use(cors());
app.use(express.json());
app.use(
session({
secret: "mySecret",
resave: false,
saveUninitialized: true,
store: memoryStore,
})
);
app.use(
keycloak.middleware({
logout: "/logout",
admin: "/",
})
);
app.use("/api/v1", router);
const port = process.env.PORT || 3000;
const start = async () => {
try {
await sequelize.authenticate();
await sequelize.sync();
app.listen(port, () => {
console.log(`[server]: Server is running at http://localhost:${port}`);
});
} catch (error) {
console.log(error);
}
};
start();
router.post("/login", userController.login);
router.get("", keycloak.protect(), userController.get);
Login is working. I get tokens, but get route always returns "Access denied". What should i do in admin console to resolve this
I had tried to play with roles in admin console, but it didnt help
You need to enable and configure Authorization Services in Keycloak. This involves setting up policies, permissions, resources, and scopes.
Resources
: The things you want to protect, such as APIs, web pages, or any other resource. It is the target of API.
Scopes
: It is an action, like a read, write, or delete to performed on the resource.
Permission
: the association between resources and scopes. It decide to allow or not to access a resource.
Policy
: Define the conditions under which access to a resource is granted.
I will leverage keycloak-nodejs-connect
example
I will show username user
is 403 error but username admin
is not error Access granted to Default Resource
.
There are many steps, Please follow me step by step.
In here
nodejs-example
realm{
"realm": "nodejs-example",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": true,
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"requiredCredentials": [ "password" ],
"users" : [
{
"username" : "user",
"enabled": true,
"email" : "sample-user@nodejs-example",
"firstName": "Sample",
"lastName": "User",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user" ],
"clientRoles": {
"account": ["view-profile", "manage-account"]
}
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"scopeMappings": [
{
"client": "nodejs-connect",
"roles": ["user"]
}
],
"clients": [
{
"clientId": "nodejs-connect",
"enabled": true,
"publicClient": true,
"baseUrl": "/",
"adminUrl" : "http://localhost:3000/",
"baseUrl" : "http://localhost:3000/",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": []
},
{
"clientId": "nodejs-apiserver",
"enabled": true,
"secret": "secret",
"redirectUris": [
"http://localhost:3000/*"
],
"webOrigins": [
"http://localhost:3000/*"
],
"serviceAccountsEnabled": true,
"authorizationServicesEnabled": true,
"authorizationSettings": {
"resources": [
{
"name": "resource",
"type": "urn:nodejs-apiserver:resources:default",
"ownerManagedAccess": false,
"uris": [
"/*"
],
"scopes": [
{
"name": "view"
},
{
"name": "write"
}
]
}
]
}
}
]
}
Result of nodejs-example
realm import
In nodejs-apiserver
client has default resource
with write
and view
scopes.
I will show this resource can be accessed by admin
user.
admin
userpassword is 1234
Result of setup policy
Result of setup permission
File tree
Save as index.html
under view
directory
<html>
<head>
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
background-color: #f1f1f1;
}
li a {
display: block;
color: #000;
padding: 8px 0 8px 16px;
text-decoration: none;
}
/* Change the link color on hover */
li a:hover {
background-color: #555;
color: white;
}
.nav {
float: left;
width: 250px;
}
.content {
margin-left: 270px;
}
pre {
word-wrap: break-word;
white-space: pre-wrap;
background-color: #ddd;
border: 1px solid #ccc;
padding: 20px;
}
</style>
</head>
<body>
<h1 style="width: 100%; text-align: center;">NodeJS Keycloak Example</h1>
<hr/>
<div class="nav">
<ul>
<li><a href="/login">Login</a></li>
<li><a href="/protected/resource">Protected Resource</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</div>
<div class="content">
<h2>Result</h2>
<pre id="output">
{{result}}
</pre>
<h2>Events</h2>
<pre id="events">
{{event}}
</pre>
</div>
</body>
</html>
Save as server.js
const Keycloak = require('keycloak-connect')
const hogan = require('hogan-express')
const express = require('express')
const session = require('express-session')
const app = express()
const server = app.listen(3000, function () {
const host = server.address().address
const port = server.address().port
console.log('Example app listening at http://%s:%s', host, port)
})
// Register '.mustache' extension with The Mustache Express
app.set('view engine', 'html')
app.set('views', require('path').join(__dirname, '/view'))
app.engine('html', hogan)
// A normal un-protected public URL.
app.get('/', function (req, res) {
res.render('index')
})
// Create a session-store to be used by both the express-session
// middleware and the keycloak middleware.
const memoryStore = new session.MemoryStore()
app.use(session({
secret: 'mySecret',
resave: false,
saveUninitialized: true,
store: memoryStore
}))
// Provide the session store to the Keycloak so that sessions
// can be invalidated from the Keycloak console callback.
//
// Additional configuration is read from keycloak.json file
// installed from the Keycloak web console.
const keycloak = new Keycloak({
store: memoryStore
})
// Install the Keycloak middleware.
//
// Specifies that the user-accessible application URL to
// logout should be mounted at /logout
//
// Specifies that Keycloak console callbacks should target the
// root URL. Various permutations, such as /k_logout will ultimately
// be appended to the admin URL.
app.use(keycloak.middleware({
logout: '/logout',
admin: '/',
protected: '/protected/resource'
}))
app.get('/login', keycloak.protect(), function (req, res) {
res.render('index', {
result: JSON.stringify(JSON.parse(req.session['keycloak-token']), null, 4),
event: '1. Authentication\n2. Login'
})
})
app.get('/protected/resource', keycloak.enforcer(['resource:view', 'resource:write'], {
resource_server_id: 'nodejs-apiserver'
}), function (req, res) {
res.render('index', {
result: JSON.stringify(JSON.parse(req.session['keycloak-token']), null, 4),
event: '1. Access granted to Default Resource\n'
})
})
package.json
{
"name": "nodejs-keycloak-example",
"version": "0.1.0",
"description": "Example page that demonstrates available keycloak functionality",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"author": "Roman Jurkov <winfinit@gmail.com>",
"license": "Apache-2.0",
"dependencies": {
"express": "^4.19.2",
"express-session": "^1.18.0",
"hogan-express": "^0.5.2",
"keycloak-connect": "^24.0.2"
}
}
npm install
node index.js
http://localhost:3000
Credential
username: user
password: password
Result
admin
Credential
username: admin
password: 1234
Result
User admin
can access resource
without error
This code passed and displayed Access granted
message in html
app.get('/protected/resource', keycloak.enforcer(['resource:view', 'resource:write'], {
resource_server_id: 'nodejs-apiserver'
}), function (req, res) {
res.render('index', {
result: JSON.stringify(JSON.parse(req.session['keycloak-token']), null, 4),
event: '1. Access granted to Default Resource\n'
})
})
So your "tender" resource needs to set up a policy/permission for your specific user.