How to authenticate keycloak token using node js that calls postgraphile? - node.js

I'm new on node js, and the company that i work for needs a proof of concept about postgraphile, the situation is this:
I created a node js mini server that uses postgraphile to access the data on postgres
The mini server works fine and can return data and also can use mutations.
I used keycloak-connect to try to access keycloak to authenticate the token from the request that is sent by postman but there is a problem.
If the token is valid or not it does not matter for the mini server, the only thing that seems to matter is that is a bearer token.
I tried to use other plugins (like keycloak-nodejs-connect, keycloak-verify, etc) but the result is the same, i also changed my code to use the examples in the documentation of those plugins but nothing.
This is my code: (keycloak-config.js file)
var session = require('express-session');
var Keycloak = require('keycloak-connect');
let _keycloak;
var keycloakConfig = {
clientId: 'type credential',
bearerOnly: true,
serverUrl: 'our company server',
realm: 'the test realm',
grantType: "client_credentials",
credentials: {
secret: 'our secret'
}
};
function initKeycloak(){
if(_keycloak){
console.warn("Trying to init Keycloak again!");
return _keycloak;
}
else{
console.log("Initializing Keycloak...");
var memoryStore = new session.MemoryStore();
_keycloak = new Keycloak({store: memoryStore}, keycloakConfig);
return _keycloak;
}
}
function getKeycloak(){
if(!_keycloak){
console.error('Keycloak has not been initialized. Please called init first');
}
return _keycloak;
}
module.exports = {
initKeycloak,
getKeycloak
};
My Index.js file:
const express = require('express')
const bodyParser = require('body-parser')
const postgraphile = require('./postgraphile')
const app = express()
const keycloak = require('../config/keycloak-config').initKeycloak()
var router = express.Router();
app.set( 'trust proxy', true );
app.use(keycloak.middleware());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(postgraphile);
app.get('/', keycloak.checkSso(), (req, res) => {
res.send('success');
} );
var server = app.listen(8080, () => console.log(`Server running on port ${8080}`));
Also I used this code to get the token and use the keycloak-verify plugin but got nothing:
router.get('/',keycloak.protect(),function(req, res, next) {
var token=req.headers['authorization'];
console.log(token);
try {
let user = keycloak.jwt.verify(token);
console.log(user.isExpired());
} catch (error) {
console.error(error);
}
})
I know that I lack the knowledge because I am a backend (C#) developer, can somebody help me with this?, thanks in advance.

I found the answer to my problem:
const express = require("express");
const request = require("request");
var keycloakConfig = require('../AuthOnly/config/keycloak-config').keycloakConfig;
const postgraphile = require('./postgraphile');
const app = express();
const keycloakHost = keycloakConfig.serverUrl;
const realmName = keycloakConfig.realm;
// check each request for a valid bearer token
app.use((req, res, next) => {
// assumes bearer token is passed as an authorization header
if (req.headers.authorization) {
// configure the request to your keycloak server
const options = {
method: 'GET',
url: `${keycloakHost}/auth/realms/${realmName}/protocol/openid-connect/userinfo`,
headers: {
// add the token you received to the userinfo request, sent to keycloak
Authorization: req.headers.authorization,
},
};
// send a request to the userinfo endpoint on keycloak
request(options, (error, response, body) => {
if (error) throw new Error(error);
// if the request status isn't "OK", the token is invalid
if (response.statusCode !== 200) {
res.status(401).json({
error: `unauthorized`,
});
}
// the token is valid pass request onto your next function
else {
next();
}
});
} else {
// there is no token, don't process request further
res.status(401).json({
error: `unauthorized`,
});
}});
app.use(postgraphile);
app.listen(8080);

Related

Post request stuck pending when returning a cookie

Im learning to use JWT. I setup a very simple react app and an express server that uses a json file as database to try it but when i return a cookie via res.cookie the request is stuck on pending according to my browsers network tab.
This is how my server looks
const express = require("express");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const jwt = require("jsonwebtoken");
const { JsonDB, Config } = require("node-json-db");
require("dotenv").config();
const server = express();
server.use(express.json());
server.use(cors());
server.use(cookieParser());
const db = new JsonDB(new Config("db", true, false));
server.post("/login", async (req, res) => {
const { username, password } = req.body;
const data = await db.getData("/users");
const user = data.find((user) => {
return user.username === username && user.password === password;
});
if (!user) {
return res.status(403).json({
error: "invalid login",
});
}
const token = jwt.sign(user, process.env.MY_SECRET, { expiresIn: "1h" });
return res.cookie("token", token, { httpOnly: true });
});
server.get("/logout", (req, res) => {
console.log("cleared token");
return res.clearCookie("token");
});
server.listen(3000, () => {
console.log("server listening on port 3000");
});
And this is my request in my react app
const handleSubmit = async () => {
const username = usernameRef.current.value;
const password = passwordRef.current.value;
const response = await axios.post("http://localhost:3000/login", {
username: username,
password: password,
});
console.log(response);
};
I've tried changing the order of the middlewares around and adding next() at the end but didn't work. Sending data via res.send works just fine. I've worked with express about a year ago but never ran into this.
res.cookie only set cookie. It doesn't response to client. After res.cookie, use res.send, res.json or any method to response.

JWT verification for Swagger NodeJs API middleware

I generated with Swagger a Node js API. I'm trying to add a jwt token check in my API to allow access to protected ressources. I would like to use something similar to passport js but I don't understand where I need to add the "passport.authenticate" method.
This is the way the app is initialized:
function initializeApp(swaggerOption, swaggerDoc) {
const p = new Promise((resolve, reject) => {
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(swaggerOption));
// Serve the Swagger documents and Swagger UI
app.use(middleware.swaggerUi());
resolve(app);
})
});
return p;
}
exports.initializeApp = initializeApp;
initializeApp(options, swaggerDoc ).then((app) => {
// Start the server
http.createServer(app).listen(serverPort, function () {
console.log('Your server is listening on port %d (http://localhost:%d)', serverPort, serverPort);
console.log('Swagger-ui is available on http://localhost:%d/docs', serverPort);
});
})
And an example of a controller
'use strict';
var utils = require('../utils/writer');
var Auth = require('../service/AuthService');
module.exports.authenticatePUT = function authenticatePUT (req, res, next) {
var body = req.swagger.params['body'].value;
Auth.authenticatePUT(body)
.then(function (response) {
utils.writeJson(res, response);
})
.catch(function (response) {
utils.writeJson(res, response);
});
}
Since there is no route like so I can siply follow passport js's syntax
app.get ('/ profile',
passport.authenticate ('bearer', {session: false}),
function (req, res) {
res.json (req.user);
});
and no documentation (or I don't find) for middleware methods I don't find where I can add the jw token verification. If someone has some examples or explanations I would be verry happy :)
Thanks !
I am not using passport, but a simple example is like so:
In your routes the middleware is defined just as a function to run before proceeding further. It will implicitly receive the request data.
app.post(
'/api/object/create',
auth.getToken, // << middleware evaluate
objectController.create
)
I have created my own middleware evaluation, it looks like this:
getToken: function (req, res, next) {
const bearerHeader = req.headers['authorization']
// Get the bearer token from the request
if (typeof bearerHeader !== 'undefined') {
const bearer = bearerHeader.split(' ')
const bearerToken = bearer[1]
req.token = bearerToken
next()
} else {
res.sendStatus(401)
}
}
Now, this only checks to see if we have a token at all, it is a pretty simple check and is not in any way a security check.
This is because we may want to do more checks at the objectController.create function, about who this is, and what they should be able to do next. These checks could also be done within the above function, depending on your use case.
objectController.create receives the request, and the first thing it does is check the token
const currentUser = await currentUser(req.token)
which checks if this user is someone in the database, based on decoding from the jwt signing secret
async currentUser(token) {
const decoded = jwt.verify(token, process.env.USER_AUTH_SECRET) // << very important never to commit this as a readable value in your repo, store in a local environment variable
const user = await User.findOne({
where: { email: decoded.user.email },
})
console.log(user.email)
return user
},
So, whilst this is not a Passport solution, it hopefully shows the basic process of middleware and then authenticating a jwt, which I think was your general question.
I think you want something like this:
const fs = require('fs');
const swaggerTools = require('swagger-tools');
const path = require('path');
const jsYaml = require('js-yaml');
const passport = require('passport');
const cors = require('cors');
const CONSTANTS = require('./constants');
module.exports = function initializeSwagger(app) {
// swaggerRouter configuration
const swaggerRouterOptions = {
swaggerUi: path.join(__dirname, '/swagger.json'),
controllers: path.join(__dirname, '../controllers'),
useStubs: process.env.NODE_ENV === 'development' // Conditionally turn on stubs (mock mode)
};
const swaggerDoc = jsYaml.safeLoad(fs.readFileSync(path.join(__dirname, '../api/swagger.yaml'), 'utf8'));
// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
// Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
app.use(middleware.swaggerMetadata());
// *** Where you will call your JWT security middleware ***
app.use(initializeSwaggerSecurity(middleware));
//enable CORS
app.use(cors());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(swaggerRouterOptions));
// Serve the Swagger documents and Swagger UI
if(process.env.NODE_ENV === "prod"){
app.use('/docs', (req, res, next) => {
});
} else {
app.use(middleware.swaggerUi());
}
});
};
function initializeSwaggerSecurity(middleware) {
return middleware.swaggerSecurity({
jwtAuth: (req, authOrSecDef, scopes, callback) => {
passport.authenticate('jwt', {session: false}, (err, user, info) => {
if (err) {
return callback(new Error(CONSTANTS.AUTHENTICATION.ERROR_MESSAGE_DEFAULT))
};
if (!user) {
// no user session, were we tampered? What happened?
// #param info has that detail!
// console.log('url requested: ' + req.url + ' | raw headers: ' + req.rawHeaders);
// console.log('api: passport => jwt fn() initializeSwaggerSecurity(), rejected jwt token, token tampered or user session does not exist; failed to authenticate token: ', info);
return callback(new Error(CONSTANTS.AUTHENTICATION.ERROR_MESSAGE_TOKEN))
}
else {
req.user = user;
return callback();
}
})(req, null, callback);
}
});
};
Hope this helps you or anybody else who might be looking to implement this on Swagger 2.0

NodeJS Rest API - where is the right place to call external API

I'm writing Rest API in Node.JS which use MySQL database but also external API, where I need fetch some data.
I'm using Express and "Router, Middleware, Controller, Model" Architecture and I'n not sure what is the right solution to call an external API. In every request I'm sending token that is required for external API. I show what I have now and try describe the problem what I currently have (Read comments in code please.)
(Also if you have some articles or tutorials where is describe how right write Rest API in node thats uses Router, Middleware, Controller, Model architecture please let me know)
This is the main index.js
const express = require("express");
const dotenv = require('dotenv');
const cors = require("cors");
const HttpException = require('./utils/HttpException.utils');
const errorMiddleware = require('./middleware/error.middleware');
const userRouter = require('./routes/user.route');
const axios = require("axios");
// Init express
const app = express();
// Init environment
dotenv.config();
// parse requests of content-type: application/json
// parses incoming requests with JSON payloads
app.use(express.json());
// enabling cors for all requests by using cors middleware
app.use(cors());
// Enable pre-flight
app.options("*", cors());
const port = Number(process.env.PORT || 3331);
app.use(`/api/v1/users`, userRouter);
// This is an solution that works but I thinks is and nasty way how to do it
// You can see here how I need to call external API
app.get('/api/v1/test', (req, res, next) => {
const token = req.headers.token;
const respond = await axios.get(EXTERNAL_API_ENDPOINT, {
headers: {
cookie: `token=${token}`
}
});
});
// 404 error
app.all('*', (req, res, next) => {
const err = new HttpException(404, 'Endpoint Not Found');
next(err);
});
// Error middleware
app.use(errorMiddleware);
// starting the server
app.listen(port, () =>
console.log(`Server running on port ${port}!`));
module.exports = app;
user.route.js
const express = require('express');
const router = express.Router();
const userModel = require('../models/user.model');
const awaitHandlerFactory = require('../middleware/awaitHandlerFactory.middleware');
router.get('/currentUser', awaitHandlerFactory(userModel.getCurrentUser));
router.get('/logout');
module.exports = router;
I also have an Auth middleware to check token validation where i need to call external API which validate user.
Auth.middleware.js
const HttpException = require('../utils/HttpException.utils');
const UserModel = require('../models/user.model');
const dotenv = require('dotenv');
dotenv.config();
const auth = (roles) => {
return async function (req, res, next) {
try {
const token = req.headers.token;
if (!token) {
throw new HttpException(401, 'Access denied. No credentials sent!');
}
/* here I need call the external API and think that I should call it from
UserModal?, but I can't because Modal doesn't have req (should I sent it
in function parmas? like this?)*/
const user = await UserModel.getCurrentUser(token, params);
if (!user) {
throw new HttpException(401, 'Authentication failed!');
}
if(!user.active || user.active !== 'Y'){
throw new HttpException(401, `User ${user.userName} is not active!`);
}
// if the user role don't have the permission to do this action.
// the user will get this error
if (roles.length && !roles.includes(user.role)) {
throw new HttpException(401, 'Unauthorized');
}
// if the user has permissions
req.currentUser = user;
next();
} catch (e) {
e.status = 401;
next(e);
}
}
}
module.exports = auth;
I`m not sure how to handle this. I have to provide token and some data to call external API. Im not sure, if I shloud call Model or do it by Controller (or middleware?).
Where and how I should do it and why? Thanks!
Looks alright to me. Really depends on whether or not the API call needs to be made every request. Let say the user makes a request to your API, which then makes a request to an external API to authenticate that user, you're gonna need the auth token from every request.
You can organize your code a little better though. As example:
šŸ“¦api
ā”£ šŸ“‚controllers
ā”ƒ ā”— šŸ“œtest.js
ā”£ šŸ“‚services
ā”ƒ ā”— šŸ“œEXTERNAL_API_ENDPOINT.js
ā”— šŸ“œindex.js
api/services/EXTERNAL_API_ENDPOINT.js
const axios = require("axios");
async function getService(token) {
return axios.get(EXTERNAL_API_ENDPOINT, {
headers: { cookie: `token=${token}` }
});
}
module.exports = getService;
api/controllers/test.js
const getService = require("../services/EXTERNAL_API_ENDPOINT");
async function test(req, res, next) {
const token = req.headers.token;
const respond = await getService(token)
}
module.exports = test;
api/index.js
const test = require("./controllers/test");
app.get('/api/v1/test', test);

403 forbidden expressjs backend

GET http://localhost:5000/booksIdea/show 403 (Forbidden)
i check the token in the website https://jwt.io/ i got invalid signature so i guess why the problem came from but i ignore how to fix it
i searched abt this error and this is what i found : Receiving a 403 response is the server telling you, ā€œIā€™m sorry. I know who you areā€“I believe who you say you areā€“but you just donā€™t have permission to access this resource. Maybe if you ask the system administrator nicely, youā€™ll get permission. But please donā€™t bother me again until your predicament changes.ā€
API GET function on front end:
import axios from 'axios'
export const ShowBooks = () => {
let token = localStorage.getItem("usertoken")
return axios.get("http://localhost:5000/booksIdea/show", {
headers: {
Authorization: `Bearer ${token}`, //here remove + in template litereal
},
})
.then(res => {
console.log("Success")
})
.catch(error => {
console.log(error)
})
}
backend app.js
const express = require('express')
var cookieParser = require('cookie-parser')
const app = express()
var cors = require('cors')
var bodyParser = require('body-parser')
const port = 5000
const routes = require("./routes");
const con = require('./db')
var cors = require('cors')
app.use(cors())
// database connect
con.connect(function(err) {
if (err) throw err;
console.log("Connected!");
});
//cookie
app.use(cookieParser())
//routes
// support parsing of application/json type post data
app.use(bodyParser.json());
//support parsing of application/x-www-form-urlencoded post data
app.use(bodyParser.urlencoded({ extended: true }));
app.use("/", routes);
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
here is routes
var express = require('express')
var router = express.Router()
var Controller = require('./controller')
var authController = require('./authController')
var BooksIdeaController = require('./BooksIdeaController')
router.post('/register',Controller.register);
router.post('/login',authController.login);
router.post('/booksIdea/:id',authController.verify,BooksIdeaController.addComment)
router.post('/booksIdea/addbook',authController.verify,BooksIdeaController.addBookIdea)
router.get('/booksIdea/show',authController.verify,BooksIdeaController.showBookIdea)
router.put('/booksIdea/edit/:id',authController.verify,BooksIdeaController.UpdateBookIdea)
router.delete('/booksIdea/delete/:id',authController.verify,BooksIdeaController.DeleteBookIdea)
module.exports = router;
authController
const con = require('./db');
var bcrypt = require('bcrypt');
let jwt = require('jsonwebtoken');
const express = require('express')
var cookieParser = require('cookie-parser')
const app = express()
module.exports.login=function(req,res){
var username=req.body.name;
var password=req.body.password;
con.query('SELECT * FROM users WHERE username = ?',[username], function (error, results, fields) {
if (error) {
res.json({
status:false,
message:'there are some error with query'
})
}else{
if(results.length >0){
bcrypt.compare(password, results[0].password, function (err, result) {
if (result == true) {
jwt.sign({user:results},'configSecret',(err,token)=>{
res.json({
token:token
})
});
// res.json({
// status:true,
// message:'successfully authenticated'
// })
} else {
res.json({
status:false,
message:"username and password does not match"
});
}
});
}
else{
res.json({
status:false,
message:"username does not exits"
});
}
}
});
}
module.exports.home=function(req,res){
res.send('hello');
}
//////
// if(password==results[0].password){
// }else{
//
// }
module.exports.verify = function verifyToken(req, res, next) {
// Get auth header value
const bearerHeader = req.headers['authorization'];
// Check if bearer is undefined
if(typeof bearerHeader !== 'undefined') {
// Split at the space
const bearer = bearerHeader.split(' ');
// Get token from array
const bearerToken = bearer[1];
// Set the token
req.token = bearerToken;
// Next middleware
next();
} else {
// Forbidden
res.sendStatus(403);
}
}
How can I fix this error? thank you in advance for your help
Check your localstorage localStorage.getItem("usertoken")
Your token can be:
missing or undefined
incorrect token - probably a typo

Authorization in a NodeJS Express REST server

I am building a REST server in nodejs using express.
I would like to allow certain users to perform certain calls.
i.e. have an admin who can edit other users and see reports, where a user can only perform simple actions.
I was trying to use passport.js and passport-ldapauth, and also I would like to perform different queries for authentication (check credentials) and authorization (check if the user is part of a group).
var fs = require('fs');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var passport = require('passport');
var LdapStrategy = require('passport-ldapauth');
var app = express();
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Allow self signed certificates
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
app.use('/', index);
app.use('/users', users);
var OPTS = {
server: {
url: 'ldaps://...',
bindDN: 'cn=Manager,dc=com',
bindCredentials: 'secret',
searchBase: 'ou=people,dc=com',
searchFilter: '(uid={{username}})',
tlsOptions: {
ca: [fs.readFileSync('/path/to/certificate.crt')]
}
},
handleErrorsAsFailures: true,
failureErrorCallback: (err) => console.log(err)
};
passport.use(new LdapStrategy(OPTS));
passport.use('test', new LdapStrategy(OPTS));
app.use(passport.initialize());
app.post('/login', function(req, res, next) {
passport.authenticate('ldapauth', function(err, user, info) {
if (err) return next(err);
if (!user) return res.status(401).send(info);
res.json(user);
// req.logIn(user, function(err) {
// if (err)
// console.error(err);
// if (err) return next(err);
// return res.json(user);
// })
})(req, res, next);
});
The passport-ldapauth strategy does not allow you to perform any additional checks or queries as far as I know from reading over the documentation. The strategy and Passport in general is aimed at making the login/authentication process seamless and easy as possible. So any additional constraints will need to be handled on your own.
With that said, passport-ldapauth utilizes ldapauth-fork underneath which in turn uses ldapjs. You can try to utilize ldapjs as shown here and here, but I think the easiest solution would be to use ldapauth-fork directly.
We first need to set up ldapauth-fork, so we'll use the following example app/ldap/index.js:
const LdapAuth = require('ldapauth-fork')
const ldap = new LdapAuth({
url: 'ldaps://...',
bindDN: 'cn=Manager,dc=com',
bindCredentials: 'secret',
searchBase: 'ou=people,dc=com',
searchFilter: '(uid={{username}})',
tlsOptions: {
ca: [fs.readFileSync('/path/to/certificate.crt')]
})
ldap.on('error', (err) => { throw err })
module.exports = ldap
Our example app/controllers/auth.js could look something like this:
const jwt = require('jsonwebtoken')
const ldap = require('../ldap')
const { User } = require('../database/models') // mongoose model
const Promise = require('bluebird')
exports.login = async (req, res) => {
const { username, password } = req.body
if (!username || !password) {
res.status(400
res.json({ error: 'Missing username or password.' })
return
}
// ldapauth-fork doesn't support Promises.
// You can try to promisfy it, but I prefer this.
// I've named it `profile`, but you can name it whatever you want.
const profile = await Promise.fromCallback(cb => ldap.authenticate(username, password, cb))
// Since this is a REST API, we need to send back a token.
// For this example, we're creating it by hand.
const token = jwt.sign({ user: profile }, 'secret', {})
// Use epoch time from the token instead of generating it ourselves.
const { exp } = jwt.verify(token, 'secret')
// Finally send the token.
// By convention, the keys are snake case.
res.json({
access_token: token,
token_type: 'Bearer',
expires_in: exp,
user: profile
})
}
Now that we have our token created, we need a way to verify that token. To do that we need to write our own middleware. So for example app/middleware/valid-token.js:
const jwt = require('jsonwebtoken')
exports.needsAdminAccess = (req, res, next) => {
// This token should have already been validated by the `requiresToken` middleware
let token = req.header('authorization').split(' ')[1]
token = jwt.verify(token, 'secret')
// Let's check if they are in the admin group
// Remember that we set the user/profile value in the controller.
if (!token.user.dn.includes('ou=ADMIN')) {
next(new Error('You must be an admin to access this route.'))
return
}
// Any additional checks would go here.
// ...
// If everything is fine then call next to let the request continue.
next()
}
exports.requiresToken = (req, res, next) => {
// Assuming the token is in the header as Authorization: Bearer token
let token = req.header('authorization').split(' ')[1]
// Make sure our secret key matches
token = jwt.verify(token, 'secret')
// Additional checks of the token should be done here as well.
// ...
// Don't forget to call next if all is good
next()
}
Finally we use the middleware wherever you define your routes, for example:
const express = require('express')
const app = express()
const { requiresToken, needsAdminAccess } = require('./middleware/valid-token')
// This route needs a valid token, but not admin rights
app.get('/user', requiresToken, (req, res) => { })
// This route needs a valid token AND admin rights
app.get('/admin', requiresToken, needsAdminAccess, (req, res) => { })
I wrote everything from scratch to hopefully paint a clear picture on how everything works. You can use other packages to validate the token for you, but we need to do verify specific things so we write our own.

Resources