Resolving Keycloak Client Adapter Deprecation for Node.js Express Boilerplate using Passport with OpenID Connect Client - express

I had previously built a Keycloak Express boilerplate using the node client adapter (keycloak-connect). However, Keycloak has deprecated their client adapters for node, so I looked for a solution and found a suggestion to integrate Passport with the OpenID Client for Keycloak on this Stack Overflow question (NodeJS + Keycloak without express). I followed the instructions in this Medium article (https://medium.com/keycloak/keycloak-express-openid-client-fabea857f11f) to build my Keycloak Express boilerplate, but it didn't work for me. My register and login endpoints are functioning properly, but my protected route is not working as expected.Could anyone provide a template or guide on how to properly configure Passport.js? I have attached my current code for reference.
import express, {
Express,
Request,
Response
} from 'express';
import 'reflect-metadata';
import bodyparser from 'body-parser';
import userRouter from './api/routes/userRoutes';
import expressSession from 'express-session';
import cookieParser from 'cookie-parser'
import connection from './database/connection';
import cors from 'cors';
import { corsOptions } from './config/corsOptions';
import { credentials } from './api/middlewares/credentials';
import { Issuer, Strategy } from 'openid-client';
import passport from 'passport';
import config from './config';
import KeycloakStrategy from "#exlinc/keycloak-passport";
//GETTING PORT FROM .ENV FILE:
const PORT = config.port || 3000;
const app: Express = express();
var memoryStore = new expressSession.MemoryStore();
app.use(
expressSession({
secret: 'another_long_secret',
resave: false,
saveUninitialized: true,
store: memoryStore
})
);
app.use(passport.initialize());
app.use(passport.authenticate('session'));
Issuer.discover('http://localhost:8080/realms/Demo').then(function (oidcIssuer) {
var client = new oidcIssuer.Client({
client_id: 'keycloak_practice',
client_secret: 'hkvogjo7jziNV3X4hR2rUILQikNKHpSL',
redirect_uris: ['http://localhost:8000/private'],
response_types: ['code'],
})
passport.use('oidc', new Strategy({client}, (tokenSet, userinfo, done)=>{
return done(null, tokenSet.claims());
})
)
});
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(user, done) {
done(null, user);
});
app.use(credentials)
app.use(cors(corsOptions));
app.use(express.json());
app.use((bodyparser.urlencoded({ extended: true })));
app.use(cookieParser());
//CONNECTING TO DATABASE:
connection();
app.use('/user', userRouter);
//CONNECTION TO PORT:
app.listen(PORT, () => {
console.log(`This application is listening on port ${PORT}`);
});
Here userRouter.ts
import express, { Router } from 'express';
import { loginUserController, logoutUserController, protectedRouteController, refreshTokenController, registerUserController } from '../controllers/userController';
import passport from 'passport';
const userRouter: Router = express.Router();
userRouter.post('/register', registerUserController);
userRouter.post('/login', loginUserController);
userRouter.get('/refreshToken', refreshTokenController);
userRouter.post('/logout', logoutUserController);
//protected route
userRouter.get('/private', passport.authenticate('oidc'),protectedRouteController);
// keycloak.protect()
export default userRouter;
Request for guidance on properly configuring Passport.js. Could anyone share a template or step-by-step guide on how to set up Passport.js for keycloak effectively?

Related

Swagger UI not getting generated for serverless Express api

My code structure is as follows-
src
-handler.ts
-routes
--user.ts
serverless.yml
node_modules
..etc
Code in 'handler.ts' for is as follows-
import express, { Express, Request, Response, NextFunction } from "express";
import serverless from "serverless-http";
import userRouter from "./routes/user";
import cookieParser from "cookie-parser";
import swaggerUi = require('swagger-ui-express');
import swaggerJsDoc = require('swagger-jsdoc');
const app: Express = express();
app.use(express.json());
app.use(cookieParser());
const swaggerOptions = {
swaggerDefinition: {
info: {
version: "1.0.0",
title: "User service",
description: "user service APIs",
contact: {
name: "Sid"
}
}
},
apis: ["./routes/*.ts"]
};
const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/swagger',swaggerUi.serve, swaggerUi.setup(swaggerDocs,{
swaggerOptions: {
url: "api-docs"
},
}));
app.use('/', userRouter);
module.exports.handler = serverless(app);
After I deploy this lambda using the serverless, my APIs are working fine the swagger-ui URL is not working. Please help

REST API router forward to Apollo GraphQL endpoinr

I have a node/express/Apollo application to provide GraphQL service to the frontend application. I also have the REST API endpoing in the application to provide service for legacy applications. I want to forward the REST API calls to the GraphQL endpoint. For example:
From
GET /api/roles
to
POST /graphql
{ *body* }
I tried like this:
// app.js
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import routes from './routes';
const port = process.env.PORT || 8088;
const app = express();
app.use('/api/roles', routes.role);
const server = new ApolloServer({
......
},
});
server.applyMiddleware({ app, path: '/graphql' });
app.listen({ port: port }, () => {
console.log(`Apollo Server on http://localhost:${port}/graphql`);
});
// routes/role.js
import { Router } from 'express';
const router = Router();
router.get('/', (req, res, next) => {
req.url = '/graphql';
req.originalUrl = '/graphql';
req.method = 'POST';
req.body = `
{
findRoles {
data {
roleId
name
}
}
}`;
return router.handle(req, res, next);
});
It doesn't work and gives the error "Cannot POST /graphql". Any idea how to do it?

upgrading to apollo-server-express 2.0.0 context missing

Before the upgrade we were having
import express from 'express';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
const app = express();
app.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => ({
schema,
tracing: true,
context: { req },
})),
);
app.use(
'/graphiql',
graphiqlExpress({
endpointURL: '/graphql',
}),
);
In our resolvers we could get the req and set req.session.token as follows,
const customResover = {
Query: {
custom: async (root, args, context) => {
console.log(' resolver called with args', args);
const { req } = context;
... fetch token info and set
req.session.token = ${token};
...
but with the upgrade to version 2.0.0 the code is changed to following and I am not sure how to fix the CustomResolver, to set the session token, any idea how the above could be accomplished ?
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import { typeDefs, resolvers } from './schema/';
const app = express();
const apollo = new ApolloServer({
typeDefs
resolvers,
engine: false
});
apollo.applyMiddleware({
app,
});
https://www.apollographql.com/docs/apollo-server/migration-two-dot.html#request-headers
const apollo = new ApolloServer({
typeDefs
resolvers,
context: ({ req }) => ({ req })
engine: false
});
Solves it but got an issue with Cookie with token not getting to the browser.

Express: unable to access route from browser due to accept:application/javascript header missing

I'm new to express. I have a Vue application running on express. I have some API routes that I'm able to access using axios through the browser. To access those routes using postman I have to have the header:
accept: application/javascript
for it to return the result of the actual API. If I don't use this header, I get the generated index.html from webpack. I need to reuse one of these routes to return excel/pdf, based on a parameter and have it accessible via a link on the page.
Here's my server.js - based on https://github.com/southerncross/vue-express-dev-boilerplate
import express from 'express'
import path from 'path'
import favicon from 'serve-favicon'
import logger from 'morgan'
import cookieParser from 'cookie-parser'
import bodyParser from 'body-parser'
import webpack from 'webpack'
const argon2 = require('argon2');
const passport = require('passport')
const LocalStrategy = require ('passport-local')
const session = require('express-session')
import history from 'connect-history-api-fallback'
// Formal(Prod) environment, the following two modules do not need to be introduced
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import config from '../../build/webpack.dev.conf'
const app = express()
app.set('trust proxy', true)
app.set("view engine", "pug")
app.set('views', path.join(__dirname, 'views'))
app.use ('/', require('./routes'))
app.use(session({
secret: process.env.SESSION_SECRET || 'secretsauce',
resave: false,
saveUninitialized: true
}))
app.use(history())
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
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')))
const compiler = webpack(config)
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: {
colors: true
}
}))
app.use(webpackHotMiddleware(compiler))
////////// PASSPORT ///////////////////////
app.use (passport.initialize ());
app.use (passport.session ());
async function authenticateUser (username, password) {
//...
}
passport.use (
new LocalStrategy (async (username, password, done) => {
const user = await authenticateUser (username, password)
if (!user) {
return done (null, false, {
message: 'Username and password combination is wrong',
});
}
delete user.password;
return done (null, user)
})
);
// Serialize user in session
passport.serializeUser ((user, done) => {
done (null, user);
});
passport.deserializeUser (function(user, done) {
if(user === undefined || !user || Object.keys(user).length === 0)
return done(null, false)
else
done (null, user);
});
//////////// passport end ///////////////
app.set("view engine", "pug")
app.use(express.static(path.join(__dirname, 'views')))
app.get('/', function (req, res) {
res.sendFile('./views/index.html')
})
app.get('/success', function (req, res) {
res.render('./views/success')
})
app.use ('/api', require('./api'))
// catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new Error('Not Found')
err.status = 404
next(err)
})
app.use(function (err, req, res) {
res.status(err.status || 500)
res.send(err.message)
})
let server = app.listen(80)
export default app
And here's a bit of api.js
const {Router} = require ('express')
const router = Router()
router.get('/whome', function(req, res){
logger.info('whome', req.user)
return res.json(req.user)
})
router.get ('/hello', auth.isAuthenticated, async (req, res) => {
res.json ({text:'hello'})
})
module.exports = router
I can call http://localhost/api/hello from postman with the accept:application/javascript header and I get:
{
"text": "hello"
}
as expected. But if I call the same URL from the browser (and it's not sending that header), I get the created bundle index.html. How can I access these routes from the browser?
You have two options.
First one, try to add this in your server:
app.options('*', cors())
before to: app.set("view engine", "pug")
If that doesnt work, try to install this addon in your Google Chrome browser to test.
Allow-Control-Allow-Origin: *
And enable it. (The icon should be green instead of red).
Why this happens?
The request that's being made is called a preflight request.
Preflight requests are made by the browser, as CORS is a browser security restriction only - This is why it works in Postman, which is, of course, not a browser.
Reference: Preflight request

Express session not storing session

So I'm building a web application using isomorphic-redux and React.js. I'm currently trying to get basic authentication working using passport in Node.js. However I have hit a problem in which my cookie session does not seem to be loaded in browser when I try to login.
Here is a cut down version of the code I've written so far.
Server.js
import Express from 'express';
import passport from 'passport';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import expressSession from 'express-session';
import serverConfig from './config';
const app = new Express();
// Apply body Parser and server public assets and routes
app.use(cookieParser());
app.use(bodyParser.json({ limit: '20mb' }));
app.use(bodyParser.urlencoded({ limit: '20mb', extended: true }));
app.use(expressSession({
secret: serverConfig.sessionSecret,
resave: false,
saveUninitialized: false
}));
import pp from './passport';
pp();
app.use(passport.initialize());
app.use(passport.session());
passport.js
import passport from 'passport';
import mongoose from 'mongoose';
import path from 'path';
import User from './models/user.model';
import local from './strategies/local';
const pp = () => {
// Serialize sessions
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// Deserialize sessions
passport.deserializeUser(function(id, done) {
User.findOne({
_id: id
}, '-salt -password', function(err, user) {
done(err, user);
});
});
local();
}
export default pp;
./strategies/local.js
import passport from 'passport';
import passportLocal from 'passport-local';
import mongoose from 'mongoose';
const LocalStrategy = passportLocal.Strategy;
const User = mongoose.model('User');
const local = () => {
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
},
(username, password, done) => {
User.findOne({ username: username }, (err, user) => {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Unknown user or invalid password'
});
}
if (!user.authenticate(password)){
return done(null, false, {
message: 'Unknown user or invalid password'
});
}
return done(null, user);
});
}
));
}
export default local;
When I login in it is able to serialise the session, however since I'm guessing, the session does not get stored, it does not deserialise the session.
If you want the sessions to be persistent, you need to tell the express-session module where to store them. First, you need to build a store:
var MongoStore = require('connect-mongo')(expressSession);
var oneHour = 3600;
var sessionStore = new MongoStore({
url: 'mongodb://localhost:27017/my-session-store',
touchAfter: oneHour
});
This creates a mongoDB store using connect-mongo - I'm sure this can be done with mongoose as well but I don't know how so I'll leave it as an exercise to the reader :-)
Then you can tell the express-session module to use this store:
app.use(expressSession({
secret: serverConfig.sessionSecret,
store: sessionStore, // <---- added store information!
resave: false,
saveUninitialized: false
}));