Docker: NodeJs + Keycloak — Protegendo sua API

Raviel Chausse
6 min readOct 20, 2021
docker-nodejs-keycloak

Bom dia / boa tarde / boa noite

Nesse passo a passo vamos aprender como proteger uma API REST NodeJs com as configurações Keycloak que fizemos no artigo passado (Docker: Keycloak + MySQL — Auth OpenID Connect). Para isso, vamos criar um API NodeJs um pouco mais estruturada que nos artigos passados.

Primeiro Etapa

Para começar, vamos colocar nossa instancia do Keycloak que está previamente configurada para roda em nossa máquina.

docker-desktop

Criando a Aplicação NodeJs

Vamos criar uma nova pasta chamada docker-nodejs-keycloak onde vamos colocar os nossos arquivos do docker e do NodeJs. Agora você pode abrir esta pasta em qualquer Editor de Texto ou um IDE como o VSCode.

Certifique-se de que o NodeJs esteja instalado em seu ambiente de desenvolvimento. Confirme se o node e o npm estão instalados executando os seguintes comandos em seu terminal:

windows terminal

Para melhor organizar o nosso código do NodeJs, vamos criar uma pasta src onde vai ficar de fato nossa aplicação node. Na pasta raiz, vamos criar um arquivo dockerfile com o seguinte código:

FROM node:14.17.5-buster-slim
EXPOSE 3000
RUN useradd --user-group --create-home --shell /bin/false app
RUN npm i -g npm@6.14.14
ENV HOME=/home/app
ENV TZ=America/Sao_Paulo
USER root
WORKDIR $HOME/src/
COPY ./src/* $HOME/src/
RUN npm i
RUN chown -R app:app $HOME/*
USER app
CMD [ "npm", "start" ]

Ainda na pasta raiz, vamos criar um arquivo docker-compose.yml como abaixo:

version: '3.8'volumes:
src:
services: nodejs:
build:
context: .
container_name: MY_NODEJS
environment:
NODE_PORT: 3000
ports:
- 3000:3000
volumes:
- ./src/:/home/app/src/

Na pasta src, vamos criar um arquivo package.json com o conteúdo abaixo:

{
"name": "docker-nodejs-keycloak",
"version": "1.0.0",
"description": "Docker: NodeJs + Keycloak - Protegendo sua API",
"main": "index.js",
"scripts": {
"start": "nodemon index",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Autor Medium",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"express-session": "^1.17.2",
"keycloak-connect": "^15.0.2"
},
"devDependencies": {
"nodemon": "^2.0.13"
}
}

Após a criação do arquivo package.json, no terminal, navegue até a pasta src e rode o comando abaixo para instalar as dependências do projeto:

npm i

Crie um novo arquivo chamado index.js como abaixo:

// IMPORTAÇÃO DOS MODULOS DO NPM
const http = require('http')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
// CRIAÇÃO DO EXPRESS APP
const app = express()
// USE MIDDLEWARE
app.use(bodyParser.json())
// USE CORS
app.use(cors())
// DEFINIÇÃO DA ROTA BASE
app.get('/', (req, res) => res.send('Server is up!'))
// CRIAÇÃO DO SERVIDOR HTTP
const server = http.createServer(app)
const port = process.env.NODE_PORT || 3000
// FAZ O SERVIDOR RESPONDER NA PORTA 3000
server.listen(port, () => console.log('Servidor rodando na porta %s.', port))

Salve o arquivo, vá para o seu terminal na pasta raiz do projeto e digite o seguinte comando:

docker-compose up -d --build
nodejs

Isso vai iniciar o container do NodeJs. Para testar a aplicação, abra seu navegador e vá para localhost:3000 e a mensagem Server is up! aparecerá no navegador.

Navegador

Criando a TestController

Vamos criar uma nova pasta controllers e vamos criar um novo arquivo test-controler.js na pasta criada com o conteúdo abaixo.

const express = require('express')
const router = express.Router()

router.get('/anonymous', function(req, res){
res.send('Hello Anonymous')
})
router.get('/user', function(req, res){
res.send('Hello User')
})

router.get('/admin', function(req, res){
res.send('Hello Admin')
})

router.get('/all-user', function(req, res){
res.send('Hello All User')
})

module.exports = router

Importe test-controller.js e adicione o router testController no express em index.js após a definição da rota base.

const testController = require('./controllers/test-controller.js')
app.use('/test', testController)

A função app.use vincula o router testController na rota /test. Agora, todas as requests que a nossa API receberá em /test, serão tratadas pela nossa controller test-controller.js. As rotas /anonymous, /user, /admin, /all-user na test-controller.js são na verdade, sub-rotas da rota /test.

Vamos chamar as APIs REST em test-controller.js com CURL, uma por uma.

curl -X GET "http://localhost:3000/test/anonymous"
curl -X GET "http://localhost:3000/test/user"
curl -X GET "http://localhost:3000/test/admin"
curl -X GET "http://localhost:3000/test/all-user"
cmder

Como você pode ver, todas as APIs não exigem autenticação ou autorização. Agora vamos proteger esses endpoints da API.

NodeJs x Keycloak

Para configurar o NodeJs com Keycloak, siga as etapas abaixo.

Primeiro, crie uma nova pasta configs e crie um novo arquivo keycloak-config.js na pasta criada com o conteúdo abaixo. Altere o conteúdo da variável keycloakConfig Server URL e Client Id se necessário. Coloque a chave Secret do seu Client (docker-nodejs-keycloak) na aba Credentials.

const session = require('express-session')
const Keycloak = require('keycloak-connect')
let _keycloakconst keycloakConfig = {
clientId: 'docker-nodejs-keycloak',
bearerOnly: true,
serverUrl: 'http://host.docker.internal:3030/auth',
realm: 'Nodejs-Realm',
credentials: {
secret: '2d5cf1b4-c73d-4db6-9a4c-3057775313af'
}
}
function initKeycloak(app) {
if (_keycloak) {
console.warn("Trying to init Keycloak again!")
return _keycloak
}
else {
console.log('Initializing Keycloak...')
const memoryStore = new session.MemoryStore()
app.use(session({
secret: keycloakConfig.credentials.secret,
resave: false,
saveUninitialized: true,
store: memoryStore
}))
_keycloak = new Keycloak({ store: memoryStore }, keycloakConfig)
console.log("Keycloak Initialized.")
return _keycloak
}
}
function getKeycloak() {
if (!_keycloak) {
console.error('Keycloak has not been initialized. Please called init first.')
}
return _keycloak
}
module.exports = {
initKeycloak,
getKeycloak
}

É hora de inicializar o Keycloak na index.js, Adicione as linhas abaixo ao index.js após a linha const app = express ().

const keycloak = require('./configs/keycloak-config.js')
.initKeycloak(app)
app.use(keycloak.middleware())

Após essa alteração, o index.js ficará como abaixo:

VSCode

Implementar acesso baseado em Role para APIs

Para proteger os nossos endpoints do router, nos vamos utilizar o keycloak.protect() que é um Express Request Handler. Adicione o código abaixo na test-controller.js para acessar a instância Keycloak iniciada no index.js.

const keycloak = require('../configs/keycloak-config.js')
.getKeycloak()

/test/anonymous:

Esta API deve ser acessível sem nenhum token de autorização, sem restrições. Ele já atende aos nossos requisitos e não precisa de alterações adicionais.

/test/user:

Esta API deve ser acessível aos usuários com a Role user. Isso pode ser definido alterando o código conforme abaixo.

router.get('/user', keycloak.protect('user'), function(req, res) {
res.send("Hello User")
})

/test/admin:

Esta API deve ser acessível aos usuários com a Role admin. Isso pode ser definido alterando o código conforme abaixo.

router.get('/admin', keycloak.protect('admin'), function(req, res) {
res.send("Hello admin")
})

/test/all-user:

Esta API deve ser acessível aos usuários com as Roles user & admin. Isso pode ser definido alterando o código conforme abaixo.

router.get('/all-user', keycloak.protect(['user', 'admin']), function(req, res) {
res.send("Hello All User")
})

Agora o nosso arquivo test-comtroller.js deverá ficar como abaixo:

test-controller.js

Se nos testarmos novamente os nossos endpoints teremos os seguintes resultados:

cmder

Agora teste as APIs acima passando tokens dos tokens de acesso user, admin e sysadmin no Authorization header com o prefixo bearer(bearer <ACCESS_TOKEN>).

Hello user

Se o token tiver expirado, você receberá o erro 401 Access denied.

Access denied

Quando isso acontecer, basta seguir as instruções do artigo de configuração do Keycloak para fazer o refresh_token.

Espero que tenham gostado. Até a próxima!!

GitHub

github.com/rchaussetech/docker-nodejs-keycloak

--

--