Skip to content
This repository has been archived by the owner on Apr 3, 2023. It is now read-only.

Next api example #44

Closed
webdeb opened this issue Jan 27, 2020 · 10 comments
Closed

Next api example #44

webdeb opened this issue Jan 27, 2020 · 10 comments

Comments

@webdeb
Copy link

webdeb commented Jan 27, 2020

Hi, currently, you have some great examples for the frontend part.
I am looking to secure some api parts of my next api with keycloak.

Do you have a working example, that could be made public?
Thank you

@panz3r
Copy link
Contributor

panz3r commented Jan 28, 2020

Hi @webdeb ,

Thanks for your feedback.

Unfortunately I don't have any working example for the API integration part, this module is focused more towards the Frontend one and so I didn't add any API examples.

I might look into this but I don't have a clear timeframe, in the meanwhile I'd suggest you to have a look at the official Keycloak docs here which contains a pretty good example of how to integrate Keycloak on an Express-based API.

@webdeb
Copy link
Author

webdeb commented Jan 28, 2020

No problem, I just thought, because of the next package here, that you maybe also tried to secure next's api routes. I'll see if I can put a small POC together.

@webdeb
Copy link
Author

webdeb commented Jan 28, 2020

OK, just a small POC (I've used express, instead of nextjs api routes), anyway maybe someone will find it useful.

// auth.js
const session = require("express-session")
const Keycloak = require("keycloak-connect")

const kcConfig = {
  realm: "<REALM>",
  "auth-server-url": "https://<KEYCLOAK>/auth",
  "ssl-required": "external",
  "public-client": true,
  "confidential-port": 0,
}

const memoryStore = new session.MemoryStore()
const keycloak = new Keycloak({ store: memoryStore }, kcConfig)
const sessionMiddleware = session({
  secret: "mySecret",
  resave: false,
  saveUninitialized: true,
  store: memoryStore,
})

module.exports = {
  keycloak,
  sessionMiddleware,
}

Custom server

const express = require("express")
const next = require("next")
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== "production"
const { sessionMiddleware, keycloak } = require("./auth") // the file above

const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  const server = express()
  server.use(sessionMiddleware)
  server.use(keycloak.middleware())

  server.all("*", handle)

  server.listen(port, err => {
    if (err) throw err
    console.log(`> Ready on http://localhost:${port}`)
  })
})

withUser.js // Middleware

import { keycloak } from "./auth"
const protect = keycloak.protect((token, request) => {
  request.user = { ...token.content, userId: token.content.sub }
  return true
})

export default handler => async (req, res) => {
  protect(req, res, () => handler(req, res))
}

some api/route.js

import withUser from "withUser.js"
const route = (req, res) => {
  console.log(req.user)
  res.json({ success: true, url: "" })
}

export default withUser(route)

@webdeb
Copy link
Author

webdeb commented Jan 28, 2020

@panz3r please ping me, if you find a good solution with plain next-api-routes. Thank you

@panz3r
Copy link
Contributor

panz3r commented Jan 29, 2020

Hi @webdeb ,

I had some time to experiment with the integration of Keycloak and NextJS API, I came up with a solutions that relies on next-connect package, so that instead of having to replace the app router with Express and implement the glue-logic between micro and keycloak-connect, the latter can be used directly.

So it's only needed to implement the following files:

  • keycloak-auth.js
import session from 'express-session';
import Keycloak from 'keycloak-connect';

const kcConfig = {
  realm: "<REALM>",
  "auth-server-url": "https://<KEYCLOAK>/auth",
  "ssl-required": "external",
  "public-client": true,
  "confidential-port": 0,
}

const memoryStore = new session.MemoryStore();
const keycloak = new Keycloak({ store: memoryStore }, kcConfig);

const sessionMiddleware = session({
  secret: 'mySecret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore,
});

export { keycloak, sessionMiddleware };
  • api/{endpoint}.js
import nextConnect from 'next-connect';

import {
  keycloak,
  sessionMiddleware,
} from '../../../middlewares/keycloak-auth';

const handler = nextConnect();

handler
  .use(sessionMiddleware)
  .use('/', ...keycloak.middleware()) // Must be set this way since 'keycloak.middleware()' returns an array
  .use(keycloak.protect())
  .get((req, res) => {
    console.log('Keycloak data', req.kauth); // contains Keycloak user data, if authenticated

    try {
      res.status(200).json({});
    } catch (err) {
      res.status(500).json({ statusCode: 500, message: err.message });
    }
  });

export default handler;

This is still based on reusing the existing keycloak-connect package, another way would be to create a custom middleware for micro with the required methods but that would require some time and more testing.

@webdeb
Copy link
Author

webdeb commented Jan 29, 2020

@panz3r cool, this looks nice. I guess some parts could be shared. like

handler
  .use(sessionMiddleware)
  .use('/', ...keycloak.middleware()) // Must be set this way since 'keycloak.middleware()' returns an array
  .use(keycloak.protect())

Thank you, I did't know about next-connect before.

@panz3r
Copy link
Contributor

panz3r commented Jan 31, 2020

@webdeb ,

Yes, it could be reused as per next-connect documentation, like this

// Define and export a keycloakHandler inside keycloak-auth.js
const keycloakHandler = nextConnect().use(sessionMiddleware).use('/', ...keycloak.middleware());

// use it where needed
const handler = nextConnect(); 
handler.use(keycloakHandler).use(keycloak.protect()).use((req, res, next) => {
  req.hello = 'world';
  // call next if you want to proceed to next chain
  next();
});

Hope this helps, I'm going to close this issue and add a reference inside the docs.
Thanks again for your feedback and help.

@panz3r panz3r closed this as completed Jan 31, 2020
@AnsonT
Copy link

AnsonT commented Dec 4, 2020

I've been trying to add this API auth example to the UI based nextjs example, so that when you login from the webUI subsequent API calls will be authenticated. However, it doesn't look like the APIs are able to pickup the current login state of the web routes. I'm wondering if it's because the ssr provider is saving the token in the kcToken cookie, whereas this example is looking for the token in memoryStore. Would be great to see an end-to-end example of nextJS with the API paths integrated.

@jonrosner
Copy link

Hi @webdeb ,

I had some time to experiment with the integration of Keycloak and NextJS API, I came up with a solutions that relies on next-connect package, so that instead of having to replace the app router with Express and implement the glue-logic between micro and keycloak-connect, the latter can be used directly.

This does not work in my case because upon calling the protect middleware my request object is of a different kind, eg. request.protocol is undefined. Therefore the redirectUri is not valid. Did you encounter a similar problem?

@moussasarr490
Copy link

moussasarr490 commented Mar 30, 2022

This does not work anymore. It will give you "access denied". Also I found out the node-js back-end adapter is no longer supported https://www.keycloak.org/2022/02/adapter-deprecation

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants