2

I'm newbie on node, I have a rest api with express and some endpoint are with keycloak security using keycloak-connect. I need a custom response when error 403 ocurrs, ie custom message in json format. I use a handler to managed some other status like, 200, 201, 204, 404, 500, but I could not do works when keycloak throws a 403.

var memoryStore = new session.MemoryStore();

app.use(session({
  secret: 'my-secret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

var keycloak = new Keycloak({
  store: memoryStore
});

app.use(keycloak.middleware());

// keycloak security
app.get('/service/secured', keycloak.protect('realm:user'), function (req, res) {
  res.json({message: 'secured'});
});

app.get('/service/admin', keycloak.protect('realm:admin'), function (req, res) {
  res.json({message: 'admin'});
});

// 404 handler and pass to error handler
app.use((req, res, next) => {    
    next(createError(404, 'Not found'));
});

// Error Handler
app.use((err, req, res, next) => { 
    // This log never is printed when 403 ocurrs
    console.log('Error: ', err); 
    res.status(err.status || 500);
    res.send({
        error : {
            status : err.status || 500,
            message : err.message
        }
    });
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
    console.log(`Server starter on port ${PORT}`);
});
Hector
  • 691
  • 2
  • 14
  • 28

2 Answers2

1

The problem is keycloak-connect uses express middleware wrong. It uses res.end() instead next() so you can do nothing after this. You can try to rewrite this logic by adding new middleware.

...

app.use(function(req, res, next) {
  res.end = function(body) {
    if (res.statusCode === 403 && body === 'Access denied') {
      next(body);
    } else {
      res.send(body);
    }
  }
});

app.use(keycloak.middleware());

...

// handle error how you want
  • The request just timeout. How do i return a custom response object instead of the default `Access denied` text – O'Dane Brissett Jan 05 '21 at 15:54
  • @odane-brissett Do you have a router error handler? http://expressjs.com/en/guide/error-handling.html Like this: `app.use(function (err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); });` – Vladislav Sevostyanov Mar 22 '21 at 15:07
0

You can override Keycloak.prototype.accessDenied before initialization and throw new error so your handle can treat.

var memoryStore = new session.MemoryStore();

app.use(session({
  secret: 'my-secret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore
}));

// Overriding keycloak access denied to return 401 status code and custom message.
Keycloak.prototype.accessDenied = () => {
  // Considering createError will throw new Error.
  createError(401, 'Access Denied');
};

var keycloak = new Keycloak({
  store: memoryStore
});

app.use(keycloak.middleware());

// keycloak security
app.get('/service/secured', keycloak.protect('realm:user'), function (req, res) {
  res.json({message: 'secured'});
});

app.get('/service/admin', keycloak.protect('realm:admin'), function (req, res) {
  res.json({message: 'admin'});
});

// 404 handler and pass to error handler
app.use((req, res, next) => {    
    next(createError(404, 'Not found'));
});

// Error Handler
app.use((err, req, res, next) => { 
    // Now this print with error 401 and message Access Denied
    console.log('Error: ', err); 
    res.status(err.status || 500);
    res.send({
        error : {
            status : err.status || 500,
            message : err.message
        }
    });
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
    console.log(`Server starter on port ${PORT}`);
});