redirection too many times keycloak - oauth

I have been trying to use keycloak authentication and have been stuck on this for a while. This is my code
app.get('/', function(req,res){
res.render('login1');
});
app.get('/login', keycloak.protect(), function (req, res) {
res.render('dashboard', {
result: JSON.stringify(JSON.parse(req.session['keycloak-token']), null, 4)
});
});
So what is happening is, when i go to hostname/login, it redirects me to a login page of my company (we are validating the company employees with their credentials), we have a redirect uri which is http://hostname/login/* , so after keycloak.protect() executes, and user enters his credentials, it goes into infinite loop and the message on the browser is, redirected too many times.
While, ideally what should have happened is, after getting validated, it should come back to /login route, and render dashboard page we have. but it is not happening.

you forget to install the keycloak middleware inside your application, add the lines given below in your code, it will resolve the issue:
app.use(keycloak.middleware({
logout:'/logout'}));
complete code sample given below:
var session = require('express-session');
var express = require('express');
var Keycloak = require('keycloak-connect');
var memoryStore = new session.MemoryStore();
var keycloak = new Keycloak({ store: memoryStore });
var app = express();
app.use(session({
secret: 'mySecret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
var keycloak = new Keycloak({
store: memoryStore
});
app.use(keycloak.middleware({
logout:'/logout'}));
app.use('/route1', keycloak.protect(), function(req, res){
console.log("AAAA")
res.json("AAAA")
})
// Server Start
app.listen(3000, function(){
console.log("Server Started")
})

Related

how to clear user session of react-native-app-auth

After logged in by the authorization function of the react-native-app-auth lib I couldn't log in with another account until the token expired, that's because the user's session continues to be used.
OBS: I already tried to clear cookies with #react-native-cookies/cookies
I already tried using revoke, but even if the token is revoked, the user session remains in the webview
The closest to a solution I found was using the authorize function passing a logout url to the WEB application
Tried opening my own webview but logout didn't work
About the server:
Devise v4.7.3 + doorkeeper v.5.4.0 is used
The backend uses Ruby 2.7.1 and Rails 6.0.3.3
App:
React Native v0.66.3
react-native-app-auth 6.0.1
const config = {
issuer: API_URL,
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
redirectUrl: CALLBACK_LOGIN,
} as AuthConfiguration
export const login = async () => {
try {
const result = await authorize(config)
const userData = await requestUserInfo(result.accessToken)
await AsyncStorage.setItem(USER_INFO, JSON.stringify(userData.data))
await AsyncStorage.setItem(TOKEN_KEY, result.accessToken)
await AsyncStorage.setItem(
TOKEN_EXPIRATION_DATE,
result.accessTokenExpirationDate
)
return true
} catch (error) {
return false
}
}
export const logout = async () => {
await AsyncStorage.removeItem(USER_INFO)
await AsyncStorage.removeItem(TOKEN_KEY)
await AsyncStorage.removeItem(TOKEN_EXPIRATION_DATE)
}
I tried to revoke the token but it didn't work.
I expected the user to be able to switch accounts after logging out

Snoowrap requester fromAuthCode returns API Error: invalid_grant - undefined

I am able to generate the authorization url, and the code is sucessfully returned to my express endpoint. All this is done in separate methods that I won't bother showing here - simple express routes. They have no affect on the code. I have copy/pasted the auth code from my browser cookies and made a test script that simply calls these functions:
const snoowrap = require('snoowrap');
module.exports = {
getAuthURL: async (managerId, modelId) => {
return snoowrap.getAuthUrl({
clientId: process.env.RDT_CLIENT,
scope: ['privatemessages', 'identity', 'read', 'submit'],
redirectUri: process.env.REDIRECT_URI,
permanent: false,
state: `${managerId}-${modelId}`
});
},
requester: async (token) => {
let s;
try {
s = await snoowrap.fromAuthCode({
code: token,
userAgent: process.env.RDT_AGENT,
clientId: process.env.RDT_CLIENT,
redirectUri: process.env.REDIRECT_URI,
clientSecret: process.env.RDT_SECRET,
});
} catch (err) {
if (err) {
console.log(err);
}
}
return s;
}
}
require('dotenv').config();
(async () => {
const requester = await require('./reddit/snoowrap').requester('<CODE GOES HERE>');
const me = await requester.getMe();
console.log(me);
})();
Calling this function results in this error:
RequestError: API Error: invalid_grant - undefined
I have a feeling I am just making a simple mistake here. If anyone with more experience than me could show me the path I would be eternally grateful!
Edit:
Made some progress - I am able to use the grant code a single time, even though I set it to permanent. I think what is happening is when I stop the application, the instance of snoowrap that created the grant code is now destroyed, leaving a useless session cookie behind with it. Can anyone confirm this?
Edit 2:
I made a new access token from authUrl, made a call to snoowrap.me() with that token once, used updateAccessToken() method to update the token and set it to browser cookies, and tried to use the updated token for the next request and it still said invalid_grant on the second function call. Why??

msal.js cannot get the refresh_token even though offline_access is available and I can see it in the return call

I use msal.js to get access to the Microsoft Graph Api and I have gotten it working for the post part.
As you can see on the images I get the refresh_token in the response payload but not in the actual output when I console log it, how do I do this ?
Then thing is I need this refresh_token as the the place they auth their microsoft account is not the same place as the data is actually shown.
Let me explain
We are a infoscreen company and we need this to show the calendar of the people who authed when they have inserted it on the presentation.
so the flow is as follows:
They install the app and login with their Microsoft 365 account to give us access to this data. (this is the part that returns the refresh token and access Token).
They go to the presentation and insert the app in the area they want to show it.
on the actual monitor that could stand anywhere in the world the calendar would now show up.
But after 1 hour the session would expire so we need to generate a new access_token and for this we need the refresh_token.
At the step of loginPopup I can see there is a refreshToken
but when I use the data it is gone, I also tried to request token silently
Also I updated to the newest version of msal-browser.min.js version 2.1 that should support it.
async function signInWithMicrosoft(){
$(".notification_box", document).hide();
$("#table_main", document).show();
const msalConfig = {
auth: {
clientId: '{CLIENTID}',
redirectUri: '{REDIRECTURI}',
validateAuthority: false
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
forceRefresh: false
},
};
const loginRequest = {
scopes: [
"offline_access",
"User.Read",
"Calendars.Read",
"Calendars.Read.shared"
],
prompt: 'select_account'
}
try {
const msalClient = new msal.PublicClientApplication(msalConfig);
const msalClientLoggedIn= await msalClient.loginPopup(loginRequest).then((tokenResponse) => { console.log(tokenResponse); });
msalClientAccounts = msalClient.getAllAccounts();
var msalInsertAccount = true;
var tableMainAsText = $("#table_main", document).text();
if(typeof msalClientLoggedIn.idTokenClaims !== 'undefined'){
if(tableMainAsText.indexOf(msalClientLoggedIn.idTokenClaims.preferred_username)>-1){
msalInsertAccount = false;
}
if(msalInsertAccount){
var tableRow = "<tr>"+
"<td>"+msalClientLoggedIn.idTokenClaims.name+" ("+msalClientLoggedIn.idTokenClaims.preferred_username+") <span style='display: none;'>"+msalClientAccounts[0].username+"</span><input type=\"hidden\" name=\"app_config[exchange_online][]\" class=\"exchange_online_authed_account\" value=\""+msalClientLoggedIn.idTokenClaims.preferred_username+","+msalClientLoggedIn.idTokenClaims.name+"\" /></td>"+
"<td class=\"last\">Fjern adgang</td>"+
"</tr>";
$("#table_body", document).append(tableRow);
$("#table_foot", document).hide();
}
}
}catch(error){
$(".notification_box", document).show();
}
}

Make my web app authorize with google oAuth

I am trying to login with Google oAuth. But when ever i try to login with oAuth it ask for permission. From the code i realize that there need to add the authorization . I have gone through web and found another way to make the app authorize which is complete different than what i have used here. Is there any way so that i can just modify or add a function so that i will be able to make my app authorize with google oAuth ? I am using php and javascript for my web app.
var loginFinished = function(authResult)
{
if (authResult['status']['signed_in']) {
var btnLogOut=document.getElementById("social-integration-logout");
accessToken=authResult['access_token'];
expiresIn=authResult['expires_in'];
console.log(authResult);
gapi.client.load('oauth2', 'v2', function()
{
gapi.client.oauth2.userinfo.get()
.execute(function(resp)
{
var id = resp.id;
});
});
} else {
console.log('Sign-in state: ' + authResult['error']);
}
};
var options = {
'callback': loginFinished,
'approvalprompt': 'force',
'clientid': '',
'scope': 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
'requestvisibleactions': 'http://schemas.google.com/CommentActivity http://schemas.google.com/ReviewActivity',
'cookiepolicy': 'single_host_origin'
};
var renderBtn = function()
{
gapi.signin.render('btn_google_login', options);
}
Could you explain what issue you are having, i.e., what is not working? I notice that your script has 'approvalprompt': 'force', which will force the authorization dialog to always display. You may want to remove it so that returning users do not have to consent again. But I am not confident that this was your question.

Upload-Progress in compoundjs/express with bodyParser({defer:true}) fails

I try to figure out how to use the defer-property of the bodyParser used in express (and compoundjs) correctly.
The goal is to get access to the event-property which should be possible duo passing the defer-property to the bodyParser.
What happens is, that the bodyParser doesnt work at all for enctype=multipart/form-data.
In my opinion the bodyParser still should parse the request and place all relevant data in the body-Object. But the body-Object is empty for every Request I use a form with enctype=multipart/form-data. That causes several errors like authetification failure or forgery-Check.
So - whats going on here? Did I unserstand something wrong? The bodyParser just should do its job and I want to have access to the progress-event.
PS: I read about errors caused by the order I use bodyParser, sessionParser and so on.
Therefore here is my configuration (compoundjs):
module.exports = function (compound) {
var express = require('express');
var app = compound.app;
app.configure(function(){
app.use(compound.assetsCompiler.init());
app.use(express.static(app.root + '/public', { maxAge: 86400000 }));
app.set('jsDirectory', '/javascripts/');
app.set('cssDirectory', '/stylesheets/');
app.set('cssEngine', 'less');
app.set('view engine', 'ejs');
// make sure you run `npm install browserify uglify-js`
// app.enable('clientside');
app.use(express.methodOverride());
app.use(express.cookieParser('secret'));
app.use(express.session({secret: 'secret'}));
app.use(express.bodyParser({
//keepExtensions: true,
limit: 10000000, // 10M limit
defer: true
}));
app.use(app.router);
});
};
Here is the answer to my own question. I hope it will help poeple with the same problem.
First of all: You cant observe the progress-event of the fileupload in your normal app-code (e.g. controller, model).
I tried to use the before-filter which seemed to work till I realized that it destroys the method-overiding.
Thats why I had to wrote my own very simple middleware which just listens to the progress and end-event of req.form which logs the progress and calls next() when the end-event happens.
module.exports = function (compound) {
var express = require('express');
var app = compound.app;
app.configure(function(){
app.use(compound.assetsCompiler.init());
app.use(express.static(app.root + '/public', { maxAge: 86400000 }));
app.set('jsDirectory', '/javascripts/');
app.set('cssDirectory', '/stylesheets/');
app.set('cssEngine', 'less');
app.set('view engine', 'ejs');
app.use(express.bodyParser({
//keepExtensions: true,
limit: 10000000, // 10M limit
defer: true
}));
// Thats the middleware
app.use(function(req, res, next){
// Only use it if form isnt parsed yet through bodyParser
if(!req.form){next();return;}
req.form.on('progress', function(bytesReceived, bytesExpected) {
console.log('progress: '+Math.round(bytesReceived/bytesExpected*100)+'%');
req.form.on('end',function(){
console.log('fileupload finished');
next();
});
});
app.use(express.cookieParser('secret'));
app.use(express.session({secret: 'secret'}));
app.use(express.methodOverride());
app.use(app.router);
});
};
Its worth to mention that the order of the middleware-calls is very important.
First you have to call the bodyParser (with defer:true).
If that is done the Parser can parse all incoming requests for you and only delegate the forms of enctype="multipart/form-data" to you. Then your middleware can observe the upload.
After that Session and Cookies are loaded. I tried to load the session and cookies before my middleware to know whether the user which is currently uploadind has the right to do so but that caused very wired behavior. I could read the session and all seems great but all my data in the form-object became doubled. {name:'dude'} becamed {name:{0:'dude',1:'dude'}} which destroyed the method-Override, too.
This order is my only known working order.
If you have a solution to the mentioned problem with the doubled data any help would be appreciated :)
//EDIT: I got a solution for the problem above. The Probem was - of couse as always - the order of the middleware. Here again the "final" Code which works with uploadprogress and authentification through session:
module.exports = function (compound) {
var express = require('express');
var app = compound.app;
app.configure(function(){
app.use(compound.assetsCompiler.init());
app.use(express.static(app.root + '/public', { maxAge: 86400000 }));
app.set('jsDirectory', '/javascripts/');
app.set('cssDirectory', '/stylesheets/');
app.set('cssEngine', 'less');
app.set('view engine', 'ejs');
// make sure you run `npm install browserify uglify-js`
// app.enable('clientside');
// At the very first load Cookie and Session
app.use(express.cookieParser('secret'));
app.use(express.session({secret: 'secret'}));
// Load the bodyParer then with defer:true
app.use(express.bodyParser({
//keepExtensions: true,
limit: 10000000, // 10M limit
defer: true
}));
// Now comes the own middleware with access to the session _and_ the progress
app.use(function(req, res, next){
console.log('searching for files to upload...');
if(!req.form || req.form.type != 'multipart'){next();return;}
console.log('check if user is autheticated...');
if(!req.session.userId)
{
console.log('user is not authenticated. Throwing Error to prevent further upload!');
try { throw new Error("Stopping file upload..."); }
catch (e) { res.end(e.toString()); }
next();return;
}
console.log('file found - attaching events...');
req.form.on('progress', function(bytesReceived, bytesExpected) {
console.log('progress: '+Math.round(bytesReceived/bytesExpected*100)+'%');
});
req.form.on('end',function(){
console.log('fileupload finished');
next();
});
});
app.use(express.methodOverride());
app.use(app.router);
});
};

Resources