Unable to connect to Centrifugo v2.4.0 from client application nodeJS - centrifuge

Here is my client code.
let Centrifuge = require("centrifuge");
var SockJS = require('sockjs-client');
var jwt = require('jsonwebtoken');
var info = { "sub": "1", "exp": 300,"info": '{ "name": "Venkat" }' }
var secret = "<<token_hmac_secret_key>>"
var centrifuge = new Centrifuge("http://127.0.0.1:8000/connection/sockjs/", {
sockjs: SockJS
})
var gentoken = jwt.sign(info, secret, { algorithm: 'HS256'});
centrifuge.setToken(gentoken);
centrifuge.connect();
centrifuge.on("connect", function (context) {
console.log("CONNECT:", context);
// now client connected to Centrifugo and authorized
});
centrifuge.on("disconnect", function (context) {
console.log("DISCONNECT:", context);
// do whatever you need in case of disconnect from server
});
After executing the above code i am getting response as:
node src/client.js
DISCONNECT: { reason: 'invalid token', reconnect: false }

Related

Cookie does not appear to be sent via fetch or hapi server is unable to receive cookie

So I have a simple backend server created with Hapi API and the frontend I'm using fetch. These are on different ports so I have CORs enabled and all the sweet stuff. I'm currently trying to set a refresh token in the browser using a http only cookie. As far as I can verify, the http only cookie is being set in the browser when login function is completed. I'm currently trying to send the http only cookie back to the server so I can set up the refresh token route and I can't seem to send or even verify that http token is sent back to the server.
Here's the server setting.
"use strict";
require("dotenv").config();
const Hapi = require("#hapi/hapi");
const Jwt = require("#hapi/jwt");
const routes = require("./routes/routes");
exports.init = async () => {
const server = Hapi.server({
port: 3000,
host: "localhost",
routes: {
cors: {
origin: ["*"],
credentials: true,
},
},
});
require("./models");
await server.register(Jwt);
server.auth.strategy("jwt", "jwt", {
keys: { key: process.env.SECRET_KEY, algorithms: ["HS256"] },
verify: { aud: false, iss: false, sub: false, exp: true },
validate: false,
});
server.state("refresh", {
ttl: 1000 * 60 * 60 * 24,
isSecure: true,
isHttpOnly: true,
encoding: "base64json",
clearInvalid: true,
strictHeader: true,
isSameSite: "None",
});
server.route(routes);
return server;
};
process.on("unhandledRejection", (err) => {
console.log(err);
process.exit(1);
});
Here's the login request and returns the http only cookie. This part works, the http cookie is returned and set.
const validateUserAndReturnToken = async (req, h) => {
const user = await User.findOne({
$or: [{ email: req.payload.username }, { username: req.payload.username }],
});
if (user) {
const match = await bcrypt.compare(req.payload.password, user.passwordHash);
if (match) {
const token = await createToken(match);
const refreshToken = await createRefreshToken(match);
h.state("refresh", refreshToken);
return { id_token: token, user: formatUser(user) };
} else {
throw boom.notAcceptable("Username and password did not match.");
}
} else {
throw boom.notAcceptable("Username or email was not found.");
}
};
Here's the fetch request I'm using to test sending a http cookie only back. I have credential: include so I don't know what is problem?
import type { DateInfo } from "#/stores/application";
const api = "http://localhost:3000/report";
let token = localStorage.getItem("user-token");
const headers = new Headers();
headers.append("Authorization", `Bearer ${token}`);
headers.append("Content-Type", "application/json");
export const getJobReport = async (dateFilter: DateInfo) => {
let response = await fetch(
`${api}/${dateFilter.startDate}/${dateFilter.endDate}`,
{
method: "GET",
headers,
credentials: "include",
}
);
return await response.json();
};
I have checked the application tab as well as the network request so I know set cookie is being sent and set on the browser. The problem is I can't seem to get the cookie back from the browser when fetch request is sent back to the server.
Here's the code I'm using to just check the existence of the cookie. According to Hapi Doc , req.state[cookie-name] which in this case is 'refresh' should have the cookie value. Refresh is returning undefined so I went up one level and check for req.state and gets an empty object {}.
route
{
method: "GET",
path: "/report/{startDate}/{endDate}",
options: {
auth: "jwt",
state: {
parse: true,
failAction: "error",
},
validate: {
params: Joi.object({
startDate: Joi.string(),
endDate: Joi.string(),
}),
},
},
handler: handlers.report.getJobApplicationReport,
},
handler
const getJobApplicationReport = async (req, h) => {
console.log("TEST", req.state);
const start = new Date(req.params.startDate);
const end = new Date(req.params.endDate);
try {
const applications = await Application.find({
dateApplied: { $gte: start, $lt: end },
});
// 'Applied', 'In Process', 'Rejected', 'Received Offer'
const total = applications.length;
let rejectedCount = 0;
let inProcessCount = 0;
applications.forEach((app) => {
if (app.status === "Rejected") {
rejectedCount++;
}
if (app.status === "In Process") {
inProcessCount++;
}
});
return {
total,
rejectedCount,
inProcessCount,
};
} catch (error) {
console.log(error);
throw boom.badRequest(error);
}
};
I've looked through all the Hapi documentation, fetch documentation and stackoverflow question/answers but can't seem to find a solution. I can't verify whether it's the fetch request that's not sending the http only cookie or the server setting that's not parsing it. Any help to determine the issue or solution would be greatly appreciated.
I've looked through all the Hapi documentation, fetch documentation and stackoverflow question/answers but can't seem to find a solution. I can't verify whether it's the fetch request that's not sending the http only cookie or the server setting that's not parsing it. Any help to determine the issue or solution would be greatly appreciated.

Where do i put the user token for api request

I have this sample code to get the users albums, but where do I put the usertoken on the request.get call. I can't find anywhere online that shows where it goes.
"use strict";
const fs = require("fs");
const jwt = require("jsonwebtoken");
const request = require("request");
const privateKey = fs.readFileSync("AuthKey.p8").toString();
const jwtToken = jwt.sign({}, privateKey, { algorithm: "ES256", expiresIn: "180d", issuer: "", header: { alg: "ES256", kid: "" } });
console.log("token:", jwtToken, "\n");
var url = "";
url = "https://api.music.apple.com/v1/me/library/albums";
request.get(
{ url: url, auth: { bearer: jwtToken }, json: true }, (err, httpResponse, body) => { if (err) { console.error(err); } else { console.log(body.results.albums.data); }
} );
You can simply attach the token to the request with query parameter or in the Authorization header.
Putting it in the header is a better way.
You can read that for more info:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization

keycloak logout doesn't invalidate token when call a rest api

I've a React app that uses Keycloak as a authentication service. Also I've a Nodejs rest api with endpoints secured by keycloak, so the React app sends JWT when needs call an api. In Keycloak admin console I created 1 public client with users and roles.
All works fine, but the only problems is when a I logout through admin console, or
from my React application berfore that expiration time, I still can call to my app with these token.
Why my backend app doesn't validate the token with server?
My node app uses keycloak-node-connect adapter and my keycloak.json is:
{
"client-id": "my-public-client",
"bearer-only": true,
"auth-server-url": "http://localhost:8180/auth",
"realm": "my-realm"
}
Solved
I can solved my probleam like suggested in Keycloak: Access token validation end point
keycloak.config.js
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var request = require('request');
const createError = require('http-errors');
let _keycloak;
var memoryStore = new session.MemoryStore();
function initKeycloak() {
if (_keycloak) {
console.log("Trying to init Keycloak again!");
return _keycloak;
}
else {
console.log("Initializing Keycloak...");
_keycloak = new Keycloak({ store: memoryStore });
return _keycloak;
}
}
function getKeycloak() {
if (!_keycloak) {
console.error('Keycloak has not been initialized. Please called init first.');
}
return _keycloak;
}
async function validateTokenKeycloak(req, res, next) {
if (req.kauth && req.kauth.grant) {
console.log('--- Verify token ---');
try {
var result = await _keycloak.grantManager.userInfo(req.kauth.grant.access_token);
//var result = await _keycloak.grantManager.validateAccessToken(req.kauth.grant.access_token);
if(!result) {
console.log(`result:`, result);
throw Error('Invalid Token');
}
} catch (error) {
console.log(`Error: ${error.message}`);
return next(createError.Unauthorized());
}
}
next();
}
module.exports = {
memoryStore,
initKeycloak,
getKeycloak,
validateTokenKeycloak
};
app.js
const express = require('express');
const createError = require('http-errors');
const dotenv = require('dotenv').config();
const session = require('express-session');
const keycloakConfig = require('./config/keycloak.config');
const app = express();
// Keycloak
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: true,
store: keycloakConfig.memoryStore
}));
const keycloak = keycloakConfig.initKeycloak();
app.use(keycloak.middleware());
app.use(keycloakConfig.validateTokenKeycloak);
app.use("/health", require('./routes/health.route'));
// 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) => {
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}`);
});

How to get connected clients and client certificate in node-opcua server

I have a node-opcua server setup with 2 clients connected to it with Security Mode set to SignAndEncrypt. Now, I have 2 questions:
Is there a way for the server to find out how many clients are connected to it?
The server application will like to know the identity of a connected client, is there an API to get the client certificate obtained during OpenSecureChannel?
OPCUA Server can expose such information under the RootFolder.Server.ServerDiagnostics node, and the information you need shall be accessible through OPCUA.
This little node-opcua client program will show you how to do.
note:
that certain data such as security diagnostics requires a secure connection and a non-anonymous user
client_extract_server_diagnostic.ts
// this script is typescript and can be run this way
// $ npx ts-node client_extract_server_diagnostic.ts
import {
AttributeIds,
OPCUAClient,
ClientSession,
StatusCodes,
MessageSecurityMode,
SecurityPolicy,
UserIdentityInfoUserName,
UserTokenType
} from "node-opcua";
// the opcua server to connect to
const endpointUrl = "opc.tcp://localhost:48010";
// the credential
const userIdentityToken: UserIdentityInfoUserName = {
password: "secret",
userName: "root",
type: UserTokenType.UserName
};
async function extractServerStatistics(session: ClientSession) {
const nodesToRead = [
{
attributeIds: AttributeIds.Value, nodeId:"Server_ServerDiagnostics_EnabledFlag"
},
{
attributeIds: AttributeIds.Value, nodeId:"Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSessionCount" //i=2277
},
{
attributeIds: AttributeIds.Value, nodeId:"Server_ServerDiagnostics_ServerDiagnosticsSummary_CurrentSubscriptionCount" // i=2285
},
{
attributeIds: AttributeIds.Value, nodeId: "Server_ServerDiagnostics_ServerDiagnosticsSummary_CumulatedSessionCount" // i=2278
},
{
attributeIds: AttributeIds.Value, nodeId: "Server_ServerDiagnostics_ServerDiagnosticsSummary_CumulatedSubscriptionCount" // i=2278
},
{
attributeIds: AttributeIds.Value, nodeId: "Server_ServerDiagnostics_SessionsDiagnosticsSummary_SessionSecurityDiagnosticsArray" // i=3708
}
];
const dataValues = await session.read(nodesToRead);
console.log("Diagnostic enabled ? = ", dataValues[0].value.value);
console.log("Current Session Count = ", dataValues[1].value.value);
console.log("Current Subscription Count = ", dataValues[2].value.value);
console.log("Cumulated Session Count = ", dataValues[3].value.value);
console.log("Cumulated Subscription Count = ", dataValues[4].value.value);
// note reading SessionSecurityDiagnotiscArray may requires authenticated session to succeed
console.log("SessionSecurityDiagnotiscArray = ");
if (dataValues[5].statusCode === StatusCodes.Good) {
const sessionSecurityDiagnosticArray = dataValues[5].value.value;
// console.log(dataValues[5].value.value.toString());
for (const sessionSecurityDiagnostic of sessionSecurityDiagnosticArray) {
console.log(" session client certificate ", sessionSecurityDiagnostic.clientCertificate.toString("base64"));
console.log();
}
} else {
console.log(dataValues[5].toString());
}
}
( async () => {
try {
const client = OPCUAClient.create({
endpoint_must_exist: false,
securityMode: MessageSecurityMode.SignAndEncrypt,
securityPolicy: SecurityPolicy.Basic256Sha256,
});
client.on("backoff",() => console.log("still trying to connec to ", endpointUrl));
await client.connect(endpointUrl);
const session = await client.createSession(userIdentityToken);
await extractServerStatistics(session);
await session.close();
await client.disconnect();
console.log("done");
} catch(err) {
console.log("Err" , err.message);
process.exit(1);
}
})();

AccessToken is null for identity server client

I have following openid options:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "http://localhost:5000",
ClientId = "mvcClient",
ClientSecret = "secret",
RedirectUri = "http://localhost:5002/signin-oidc",
PostLogoutRedirectUri = "http://localhost:5002",
ResponseType = "code id_token",
Scope = "openid profile",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
var claims_to_keep =
n.AuthenticationTicket.Identity.Claims
.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
if (n.ProtocolMessage.AccessToken != null)
{
claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));
}
}
}
}
I see n.ProtocolMessage.AccessToken is always null.
I configured client in identity server like this:
new Client()
{
ClientId = "mvcClient",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("secret".Sha256())
},
// RequireConsent = false,
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002" },
AllowedScopes =
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.OfflineAccess.Name,
StandardScopes.Roles.Name,
"API"
}
},
I want to know why n.ProtocolMessage.AccessToken is null and how can i get its value
UPDATE
If I change Client Type to Hybrid like this:
AllowedGrantTypes = GrantTypes.Hybrid,
and ResponseType = "code id_token token:
I get invalid_request error on server
If I try to get access token like this (in notifications):
var client = new TokenClient("http://localhost:5000/connect/token", "mvcClient", "secret");
var response = client.RequestClientCredentialsAsync("testscope").Result;
var accesstoken = response.AccessToken;
claims_to_keep.Add(new Claim("access_token", accesstoken));
The result token has only one scope(i.e testscope) instead of all other scopes defined for that client.
It's null because you're not asking for an access token.
ResponseType = "code id_token" means give the client a "Authoriziation Code" and a "Id token" on the callback. To receive an access token,either
include token in ResponseType as ResponseType = "code id_token token" & update the client flow to Hybrid flow (code + token), since that's what we're now doing.
or
fetch an access token using the /token endpoint using the "Authorization Code" available on the ProtocolMessage.
The access token should not be brought back along with code and id_token.
The right way to get it is through the back channel using client id and client secret.
Add this to the Notifications block:
AuthorizationCodeReceived = async n =>
{
var tokenClient = new TokenClient(n.Options.Authority + "/connect/token", "Client_Id", "secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
else
{
string accessToken = tokenResponse.AccessToken;
//Other logic
}
}