I'm using Electron (v1.2.7) and I need session cookies to persist between app restarts (for auth).
Is it possible? I know in Chrome when you set "Continue where you left off", the session is kept but not sure if this works the same in Electron.
As a workaround, I tried storing the session cookies also as non session cookies but it failed with a generic error.
Clarification: I'm not setting the session cookies they are set by other webpages during authentication.
The default session is persistent, but if you set cookies using session.defaultSession.cookies.set() you must set the expiration date in order for the cookie to be persisted.
You can persist cookies setting the session and a expirationDate
This example was made on Angularjs
var session = require('electron').remote.session;
var ses = session.fromPartition('persist:name');
this.put = function (data, name) {
var expiration = new Date();
var hour = expiration.getHours();
hour = hour + 6;
expiration.setHours(hour);
ses.cookies.set({
url: BaseURL,
name: name,
value: data,
session: true,
expirationDate: expiration.getTime()
}, function (error) {
/*console.log(error);*/
});
};
PD: A problem that i had was if i didn't persist them, after a fews reloads my cookies get lost. So in this example i'd persist them 6 hours
If you are loading an external webpage, you should be using Electrons <webview> tag to disable nodeintegration and for other security reasons. Using a webview will give you easy access to the Partition attribute which can be set to persist (ex: persist:mypage). You can also set the partition attribute on an Electron window if needed.
I was loading an external webpage and the configuration below worked for me. By default the webpage is configured to use "session cookie" and thats why I change it to "persistent cookie" with expiration date of 2 weeks:
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const path = require('path')
const util = require('util')
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 700,
height: 500,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
partition: 'persist:infragistics'
},
icon: __dirname + '/assets/favicon.ico',
show:false
})
let cookies = mainWindow.webContents.session.cookies;
cookies.on('changed', function(event, cookie, cause, removed) {
if (cookie.session && !removed) {
let url = util.format('%s://%s%s', (!cookie.httpOnly && cookie.secure) ? 'https' : 'http', cookie.domain, cookie.path);
console.log('url', url);
cookies.set({
url: url,
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path,
secure: cookie.secure,
httpOnly: cookie.httpOnly,
expirationDate: new Date().setDate(new Date().getDate() + 14)
}, function(err) {
if (err) {
log.error('Error trying to persist cookie', err, cookie);
}
});
}
});
Note: Its important to ensure that you've set the "partition" webPreferences property as well.
A String that sets the session used by the page. If partition starts with persist:, the page will use a persistent session available to all pages in the app with the same partition. if there is no persist: prefix, the page will use an in-memory session
Origin source.
Related
I'm having trouble getting Sapper to synchronize session changes made in my server-side routes without a pageload. My example scenario is that I load my app with no user in the session, my server-side login route sets the user to the session, and I use goto to got to the dashboard.
The problem is that the session argument in the dashboard's preload function isn't populated. If I use window.location.href = '/dashboard', it is, because it's running through Sapper's page_handler. But if I do a client-only redirect, Sapper isn't sending the updated session to the client.
Any way around this? Am I using my tools wrong?
Note: I'm using connect-pg-simple and express-session, setting up sapper like this: sapper.middleware({session: (req, res) => req.session.public}).
I found my answer in the Sapper docs
session contains whatever data was seeded on the server. It is a writable store, meaning you can update it with new data (for example, after the user logs in) and your app will be refreshed.
Reading between the lines, this indicates that your app has to manually synchronize your session data.
The solution here is to manually sync the session data to the client, either with a webhook connection, a response header, or a key in the response data.
I've got a decorator I use to create a server route handler, in which I add the session data to the response. Here's a simplified version:
const createHandler = getData => (req, res) => {
res.status(200).json({data: getData(req.body), session: req.session.public})
}
Obviously there's more to it than that, e.g. error handling, but you get the idea. On the client, I wrap fetch in a helper function that I always use anyway to get my json, set the correct headers, etc. In it, I look at the response, and if there's a session property, I set that to the session store so that it's available in my preloads.
import {stores} from "#sapper/app"
const myFetch = (...args) => fetch(...args).then(r => r.json()).then(body => {
if (body.session) stores().session.set(body.session)
return body.data
})
To put it simply, after your session status changes from the front end (user just logged in, or you just invalidated his login), you should update the session store on the front end.
<script>
import { goto, stores } from '#sapper/app';
const { session } = stores();
const loginBtnHandler = () => {
const req = await fetch('/api/login', {
method: 'POST',
credentials: 'same-origin', // (im using cookies in this example)
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ........ })
});
if (req.ok) {
// here is where you refresh the session on the client right after you log in
$session.loggedIn = true; // or your token or whatever
// next page will properly read the session
goto('/');
return;
}
...
}
</script>
I'm attempting to store a session cookie after a successful firebase-authentication login. All the firebase functions appear to be executing correctly. I'm able to retrieve my idToken after logging. The Post correctly sends this idToken to the server where it is validated. However, I'm getting stuck on the last step which is to store the session cookie in my success function, which doesn't execute.
To troubleshoot this I go to the Chrome Dev Tools and access the network tab and turn on preserve log. Here I can see my post request. When I click on response it says "Failed to load response data". This at least tells me that the post is partially working. The client is at a minimum sending the idToken, but I can't verify that the response is being sent from the server. Additionally, when I navigate to the Application tab of the chrome dev tools and click refresh I don't see my session cookie there.
Here is the client side code:
btnLogin.addEventListener('click', e => {
// Get eamil and pass
// TODO: Check for real email
const email = txtEmail.value;
const pass = txtPassword.value;
// As httpOnly cookies are to be used, do not persist any state client side.
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
// Sign in
firebase.auth().signInWithEmailAndPassword(email,pass).then((userCredential) => {
// Get the user's ID token as it is needed to exchange for a session cookie.
userCredential.user.getIdToken().then((idToken) => {
// Session login endpoint is queried and the session cookie is set.
// TODO: Add CSRF protection);
$.ajax({
type: 'post',
//accepts: 'json',
data: {idToken: idToken},
//dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
success: function(data) {
console.log('MADE IT TO THE SUCCESS FUNCTION');
//document.cookie = data;
firebase.auth().signOut();
$('#loginForm').before(' \
<div class="alert alert-secondary" role="alert"> \
The login was succesful! \
</div> \
');
//setTimeout(window.location.assign('/admin-manage'), 5000);
},
error: function() {
console.log('post error');
$('#loginForm').before(' \
<div class="alert alert-danager" role="alert"> \
The idToken post failed! \
</div> \
');
},
timeout: 5000
});
});
});
});
Here is the server side code:
/* POST FOR SESSION INITIALIZATION */
app.post('/admin-login', (req, res) => {
// Get the ID token passed and the CSRF token.
var idToken = req.body.idToken.toString();
// TODO: CSRF TOKEN
// Set session expiration to 2 days.
const expiresIn = 60 * 60 * 24 * 2 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// We could also choose to enforce that the ID token auth_time is recent.
adminApp.auth().verifyIdToken(idToken).then((decodedIdToken) => {
console.log('ID TOKEN: ' + idToken);
// Only process if the user just signed in the last 5 minutes
if (new Date().getTime() / 1000 - decodedIdToken.auth_time < 5 * 60) {
// Create session cookie and store it serverside
return adminApp.auth().createSessionCookie(idToken, {expiresIn});
}
else {
throw new error('The IDToken has expired');
}
})
// eslint-disable-next-line promise/always-return
.then((sessionCookie) => {
console.log(sessionCookie);
//Set session cookie on client side
const options = {maxAge: expiresIn, httpOnly: false, secure: false};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
})
.catch((error) => {
console.log(error);
res.status(401).send();
});
});
EDIT
Here is an image of the network view from me running chrom dev tools.
CHROME DEV TOOLS APPLICATION VIEW
As you can see the post script gets canceled.
Here is a detailed view of the request headers:
enter image description here
EDIT #2
I've made so more changes to the server-side code and still haven't seen any change in results. Here is the new server-side code:
/* POST FOR SESSION INITIALIZATION */
app.post('/admin-login', (req, res, next) => {
// Get the ID token passed and the CSRF token.
var idToken = req.body.idToken.toString();
// TODO: CSRF TOKEN
// Set session expiration to 2 days.
const expiresIn = 60 * 60 * 24 * 2 * 1000;
// Create the session cookie. This will also verify the ID token in the process.
// The session cookie will have the same claims as the ID token.
// We could also choose to enforce that the ID token auth_time is recent.
adminApp.auth().verifyIdToken(idToken).then((decodedClaims) => {
console.log('DECODED CLAIMS AUD: ' + decodedClaims.uid );
// Only process if the user just signed in the last 5 minutes
if (new Date().getTime() / 1000 - decodedClaims.auth_time < 5 * 60) {
// Create session cookie and store it serverside
// eslint-disable-next-line promise/no-nesting
adminApp.auth().createSessionCookie(idToken, {expiresIn}).then((sessionCookie) => {
console.log(sessionCookie);
//Set session cookie on client side
const options = {
maxAge: expiresIn,
httpOnly: true,
secure: false
};
res.set('Content-Type', 'application/json');
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
});
}
else {
throw new error('The IDToken has expired');
}
}).catch((error) => {
console.log(error);
res.status(401).send('UNAUTHROIZED REQUEST!');
});
});
I also took a wireshark capture on my loopback adapter so that I could capture the localhost traffic. I can see the HTTP response with the set-cookie tag, but not sure what the problem is. See the image below:
HTTP Post Response Wireshark Capture
EDIT #3
I've finally made a little progress. I noticed the URI in my wireshark capture wasn't "samesite" so I added the "sameSite: 'None'" option to my set-cookie options.
So my new options are:
const options = {
maxAge: expiresIn,
httpOnly: true,
secure: false,
sameSite: 'None'
};
This fixed the issue on my localhost development environment. However, I changed the secure option to true then did a firebase deploy. After the deployment I logged into the production environment and found that the cookie would not generate. So the only question left is why it won't work in the production environment.
EDIT #4
Alright, I'm losing my mind. I changed the options back to how I had them in EDIT #3 so that I could continue working the app, and the session cookie is no longer being set again. I'm at a complete loss as to why it won't work now. I even went into chrome to clear all browsing history in hopes this would resolve the issue, but no luck. If anyone can shed light on what is happening here I would greatly appreciate it.
Is there any way i could inject a logged in github session in a electron window?
let win = new BrowserWindow({
width: 800,
height: 600,
})
win.loadURL('https://www.github.com')
Electron has a Cookies API that you can use to do just this. So for example you would have something like the following, and just set all the cookies you need to. (example taken from documentation)
const cookie = {
url: 'http://www.github.com',
name: 'dummy_name',
value: 'dummy'
}
session.defaultSession.cookies.set(cookie, (error) => {
if (error) console.error(error)
})
Also, when writing a feature such as this make sure you are being secure and responsible. Ensure this does not open a vulnerability for users and that user privacy is being respected.
I'm using cookie authentication in MVC5. My web pages rely heavily on authenticated as well as unauthenticated Ajax calls every 1-5 seconds to keep data updated. Consequently, my users never log out of the site.
My ideal scenario: If a user is actively browsing or conducting actions on my site, keep the session alive. If they have left a page open after 10 minutes, I'd like their session to timeout and I’'ll use the failing Ajax calls to redirect to a login page. I think this would best be accomplished at the controller or action level.
I tried controlling the session state behavior as suggested below but the session still did not time out. After 65 seconds of hitting ReadOnly/Public once per second, I call ReadOnly/Authorized and successfully retrieve data from it.
Here is my CookieAuthentication configuration.
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1),
});
}
My test page:
<div id="public"></div>
<div id="authorized"></div>
#section scripts{
<script>
function poll(times) {
var url = '/ReadOnly/Public';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#public').html(times + ' ' + data.test);
},
error: function (data) {
$('#public').html(times + ' ' + 'failed');
}
});
};
function checkAuth(times) {
var url = '/ReadOnly/Authorized';
$.ajax({
url: url,
dataType: 'json',
data: null,
cache: false,
success: function (data) {
$('#authorized').html(times + ' ' + data.test);
},
error: function (data) {
$('#authorized').html(times + ' ' + 'failed');
}
});
};
$(function () {
var times = 1;
setInterval(function () {
poll(times);
times++;
}, 1000);
setInterval(function () {
checkAuth(times);
}, 65000);
});
</script>
}
and test controller code (tried this with both the disabled and readonly options)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.SessionState;
namespace SessionTest.Controllers
{
[SessionState(SessionStateBehavior.ReadOnly)]
public class ReadOnlyController : Controller
{
[Authorize]
public ActionResult Authorized()
{
return Json(new
{
test = "ReadOnly and Authorized"
}, JsonRequestBehavior.AllowGet);
}
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
}
}
Maybe you need to have 2 separate web apps. One is for serving authenticated requests. Another one is for all public requests.
That's similar to how the Google Analytics script creates and maintains its own Session on Google side about your site without impacting your web application's internal session management. Otherwise, you will get stuck with the default behavior of ASP .NET the way it is handling cookies and keeps session alive.
Good luck.
I wouldn't implement a timeout in this situation. In fact I try to avoid them unless there is a fundamental and key reason why they are necessary, otherwise they just become an annoyance.
However if you do feel you need one, I would implement it in this case, by creating a separate javascript function which has a timer, and that is reset with user input. If the timer completes an ajax call is performed that executes a manual session invalidation on server side.
I would configure the listener method or class to not use session which will prevent it from being extended.
There are attributes available for both methods and controllers that provides different session modes.
More info here:
http://www.dotnet-tricks.com/Tutorial/mvc/906b060113-Controlling-Session-Behavior-in-Asp.Net-MVC4.html
Ajax calls will keep the session alive.
One approach will be to set a timeout on client side to delete cookie after some time.
I'm not sure you have anymore options.
If the calls every 5 sec are only to non-authenticated request, just keep the cookie out of the ajax request.
I think the sliding expiration is set to true by default.
I think perhaps when the call that is made to the action Public, it's made with cookie and thus extending the timeout.
public ActionResult Public()
{
return Json(new
{
test = "ReadOnly and Public"
}, JsonRequestBehavior.AllowGet);
}
If I set this below: (SlidingExpiration = false). I get the failed message.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = TimeSpan.FromMinutes(1.0),
SlidingExpiration = false
//Provider = new CookieAuthenticationProvider
//{
// OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
// validateInterval: TimeSpan.FromMinutes(30),
// regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
//}
});
I have a question about express session store memory with setting the reapInterval value. I have an example code, which will output the values of a memorystore every 5 seconds. If i now set a reapinterval of 5000, it should clean up expired session every 5 seconds right? So my example looks like this:
/**
* Module dependencies.
*/
var express = require('express');
var app = module.exports = express.createServer();
var MemStore = require('express/node_modules/connect/lib/middleware/session/memory');
var store = new MemStore({reapInterval: 5000});
// Configuration
app.configure(function(){
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.session({secret: 'your secret here', store: store, cookie: {maxAge: 30000}}));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
setInterval(function(){
console.log(new Date());
console.log(store);
}, 5000);
app.configure('development', function(){
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function(){
app.use(express.errorHandler());
});
// Routes
app.get('/', function(req, res){
res.render('index', {
title: 'Express'
});
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
And now the problem is, that if I revisit the page after 30 seconds, I get a new SID, but the old session in memorystore is still there... should it not be checked every 5 seconds and deleted?
Thx for your help!
So the first problem is a misunderstanding here. reapInterval does not do anything. MemoryStore clears cookies based on the expire time of session cookie. So there is in fact a bug in Connects MemoryStore. The way I see it the broken flow goes like this.
Set cookie to expire in X.
Get session data, has X gone by? No, ok.
(cookie expires)
Get session data, session doesn't exist, generate new one.
X has gone by but the session ID is missing because the browser expired it already.
There is a discussion regarding this here.
https://github.com/senchalabs/connect/issues/328
And a juicy quote
"short cookie sessions would be screwed I guess" --visionmedia