Cookie value is returned as undefined - remix.run

I'm using cookie storage session to hold user's token which is received from authentication. When I'm trying to set it after login and call it from the root.tsx's Loader Function, the userId is returned as undefined.
My loader function is:
export let loader: LoaderFunction = async({request, params}) => {
let userId = await getUserId(request);
console.log(userId);
return (userId ? userId : null);
}
The function which I receive the userId getUserId is defined as:
export async function getUserId(request: Request){
let session = await getUserSession(request);
let userId = session.get("userId");
if (!userId || typeof userId !== "string") return null;
return userId;
}
The getUserSession function is as:
export async function getUserSession(request: Request){
return getSession(request.headers.get('Cookie'));
}
I receive the getSession from destructring createCookieSessionStorage.
I'm creating a cookie with createUserSession function which is like:
export async function createUserSession(userId: string, redirectTo: string){
let session = await getSession();
session.set("userId", userId);
return redirect(redirectTo, {
headers: {
"Set-Cookie": await commitSession(session),
},
});
}
I also receive the commitSession from destructing createCookieSessionStorage. I used the same code from the Jokes demo app.
let { getSession, commitSession, destroySession } = createCookieSessionStorage({
cookie: {
name: "RJ_session",
secure: true,
secrets: [sessionSecret],
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
},
});

You configured the cookie to have secure: true, this makes the cookie only work with HTTPS, while some browsers allow secure cookies in localhost most don't do it so that may be causing your cookie to not be saved by the browser, making subsequent requests not receive the cookie at all.

Related

Extra information gets lost when updating user context

I've been fighting with this issue for days now and I just can't solve it. My app is built on React and Django Rest Framework. I'm authenticating users with JWT - when the user logs into the app, the React Auth context gets updated with some info about the tokens and I include some extra information in the context (namely the user email and some profile information) so that I have it easily accessible.
How I am doing this is by overwriting TokenObtainPairSerializer from simplejwt:
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token["email"] = user.email
token["information"] = Profile.objects.get(user=user).information
return token
On the frontend in my AuthContext.js:
const loginUser = async (email, password, firstLogin = false) => {
const response = await fetch(`${baseUrl}users/token/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (response.status === 200) {
setAuthTokens(data);
setUser(jwt_decode(data.access));
localStorage.setItem("authTokens", JSON.stringify(data));
if (firstLogin) {
history.push("/profile");
} else {
history.push("/");
}
} else {
return response;
}
};
Up to this point it works perfectly fine and my ReactDevTools show me that the AuthContext has all the data:
Now to the issue - once the access token has expired, the next API call the user makes gets intercepted to update the token. I do this in my axiosInstance:
const useAxios = () => {
const { authTokens, setUser, setAuthTokens } = useContext(AuthContext);
const csrfToken = getCookie("csrftoken");
const axiosInstance = axios.create({
baseURL,
headers: {
Authorization: `Bearer ${authTokens?.access}`,
"X-CSRFToken": csrfToken,
"Content-Type": "application/json",
},
});
axiosInstance.interceptors.request.use(async (req) => {
const user = jwt_decode(authTokens.access);
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
if (!isExpired) return req;
const response = await axios.post(`${baseURL}users/token/refresh/`, {
refresh: authTokens.refresh,
});
// need to add user info to context here
localStorage.setItem("authTokens", JSON.stringify(response.data));
setAuthTokens(response.data);
setUser(jwt_decode(response.data.access));
req.headers.Authorization = `Bearer ${response.data.access}`;
return req;
});
return axiosInstance;
};
export default useAxios;
But the extra information is not there. I tried to overwrite the TokenRefreshSerializer from jwt the same way as I did it with the TokenObtainPairSerializer but it just doesn't add the information
class MyTokenRefreshSerializer(TokenRefreshSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token["email"] = user.email
token["information"] = Profile.objects.get(user=user).information
print(token)
return token
It doesn't even print the token in my console but I have no clue what else I should try here.
Before anyone asks, yes I specified that the TokenRefreshView should use the custom serializer.
class MyTokenRefreshView(TokenRefreshView):
serializer_class = MyTokenRefreshSerializer
However, after a while of being logged into the application, the email and information key value pairs disappear from the context.
Any idea about how this can be solved will be much appreciated!

Axios - Request header content-type was not present in the Access-Control-Allow-Headers list - ElasticSearch

I'm new to a lot of this technology, but I think I've diagnosed my issue and need some help. I've seen numerous posts on SO regarding this issue, but none have worked, though they have helped me diagnose issue.
I believe the issue is when I send the Header Content-Type w/ my pre-flight w/ Axios, it fails. This is possibly due to lower/case upper case? The error has lower case, but I tried both on the server without luck.
Basically, if I don't specify any header and Axios uses json as content-type, it works, but as soon as I specify Content-Type my pre-flight fails (even though I think post would work..).
Here is the elasticsearch.yml
cluster.name: "docker-cluster"
network.host: 0.0.0.0
http.cors.enabled : true
http.cors.allow-origin: "*"
http.cors.allow-methods: OPTIONS,HEAD,GET,POST,PUT,DELETE
http.cors.allow-headers: X-Requested-With,X-Auth-Token,Content-Type,Content-Length
#http.cors.allow-credentials: true
Here is my JS that I'm testing BTW w/ an Office Add-In solution in Visual Studio 2017 which I think is using IE as a browser.
Main Func:
var URL = "https://elasticsearch:9200/users/_search"
const data = {
"query": {
"match": {
"name": "freesoftwareservers"
}
}
};
Do_Axios('get', URL, data, null, false)
Do_Axios('post', URL, data, null, false)
Do_Axios:
async function Do_Axios(method, URL, data, headers, withCredentials) {
return axios({
method: method,
url: URL,
withCredentials: withCredentials,
//contentType: 'application/json', // does nothing
//data: JSON.stringify(data), //Causes urlformencoded which is wrong
data: data, //caues type to be json and I get error
headers: {
//"Content-Type": "application/json"
},
})
.then(function (response) {
console.log("Axios " + method + " response:");
console.log(response)
return response;
})
.catch(function (error) {
console.log(error);
});
}
Note: I can get/post if I comment out //data but then the post doesn't run my query. If I uncomment data then Axios uses urlformencoded but that doesn't work.
For now, I've been able to search API via urlformencoded queries, but I'd like to fix my ability to POST correctly to resolve future errors. I'm unsure if issue should be pointed to Axios or Elasticsearch if I open a request.
Well, I finally figured it out. I wonder how many of the other posts I read have similar issues... anyway, the issue was w/ my NGinX proxy server. No better way to learn about CORS then to setup an API and make CORS requests via IE! Without the below, I was still able to post w/ POSTMAN to the same URL which hit my nginx server, but the call from Axios/IE/JS Evironment failed.
I found these snippets and this was the magic that needed added to my "regular" configuration:
proxy_pass_header Access-Control-Allow-Origin;
proxy_pass_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
add_header Access-Control-Allow-Headers 'X-Requested-With, Content-Type';
add_header Access-Control-Allow-Credentials true;
https://gist.github.com/sahilsk/b16cb51387847e6c3329
Here is my code as it stands, cleaned up but generic atm:
Note: I pass axios because I can't figure out how to get my Webpack to transform/polyfill my funcs in seperate js files. But I can declare axios in the main func and pass it and then I can move my funcs into separate files as needed for organization. There is likely a better way to do without passing axios and configuring webpack
Main Func:
var username = "freesoftwareservers"
var ipv4 = "192.168.1.255"
var showhelp = "false"
await Do_AddUserToES(axios,username, ipv4, showhelp)
Get_UserFromES(axios,username)
var index = "users"
var query = {
query: {
match: {
"username": username
}
}
};
Get_PostQueryToES(axios,query, index)
Funcs:
function Do_Axios(axios, method, URL, data, headers, withCredentials) {
return axios({
method: method,
url: URL,
withCredentials: withCredentials,
data: data,
headers: headers,
})
.then(function (response) {
console.log("Axios " + method + " response:");
console.log(response)
return response;
})
.catch(function (error) {
console.log(error);
});
}
function Get_ESURL(Bool_Search, Bool_Doc, Bool_Update, Opt_Index, Opt_IndexKey) {
var ESUrl = "https://elasticsearch:9200"
var ESSearch = "/_search"
var ESDoc = "/_doc"
var ESUpdate = "/_update"
var ReturnURL = ESUrl
if (Opt_Index != undefined) { ReturnURL = ReturnURL + "/" + Opt_Index }
if (Bool_Search == true) { ReturnURL = ReturnURL + ESSearch }
if (Bool_Doc == true) { ReturnURL = ReturnURL + ESDoc }
if (Bool_Update == true) { ReturnURL = ReturnURL + ESUpdate }
if (Opt_IndexKey != undefined) { ReturnURL = ReturnURL + "/" + Opt_IndexKey }
console.log("ReturnURL:" + ReturnURL)
return ReturnURL;
}
function Do_AddUserToES(axios, username, ipv4, showhelp) {
var adduser = {
"username": username,
"ipv4": ipv4,
"showhelp": showhelp
};
var URL = Get_ESURL(false, true, false, "users", username)
return Do_Axios(axios, 'post', URL, adduser, null, false);
}
function Get_UserFromES(axios, username) {
var URL = Get_ESURL(false, true, false, "users", username)
return Do_Axios(axios, 'get', URL, null, null, false);
}
function Get_PostQueryToES(axios, query, index) {
var URL = Get_ESURL(true, false, false, index)
return Do_Axios(axios, 'post', URL, query, null, false);
}

JWT is not set in header

I'm following this tutorial, currently I can log in and out with a user but when a user logs in the JWT token isn't send with the header request (I think) so I get a 401 after the router.navigate. When I reload the page I can use the token and everything works.
In my login.component.ts I have this login function:
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result === true) {
// login successful
this.router.navigate(['home']);
} else {
// login failed
this.error = 'Username or password is incorrect';
this.loading = false;
}
}, error => {
this.loading = false;
this.error = error;
});
}
This calls the login function in the authentication.service.ts:
login(username: string, password: string): Observable<boolean> {
return this.http.post(this.authUrl, JSON.stringify({username: username, password: password}), {headers: this.headers})
.map((response: Response) => {
// login successful if there's a jwt token in the response
const token = response.json() && response.json().token;
if (token) {
// store username and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify({ username: username, token: token }));
// return true to indicate successful login
alert('Success');
return true;
} else {
// return false to indicate failed login
alert('Fail');
return false;
}
}).catch((error: any) => Observable.throw(error.json().error || 'Server error'));
}
If the login is successful the user is routed to /home:
this.router.navigate(['home']);
In the home.component.ts I have a getAll function that returns all movies in the database:
getAll() {
this._dataService
.getAll<Movie[]>()
.subscribe((data: any[]) => this.movies = data,
error => () => {
'something went wrong';
},
() => {
console.log(this.movies);
});
}
This function is called on the ngOnInit:
ngOnInit(): void {
this.getAll();
}
In my app.service.ts I have the get function:
public getAll<T>(): Observable<T[]> {
if (this.authenticationService.getToken()) {
console.log(this.authenticationService.getToken());
console.log(this.headers);
return this.http.get<T[]>('/api/movies/all', {headers: this.headers});
}
}
But when I log in I get this error after being routed to the home page:
GET http://localhost:4200/api/movies/all 401 (Unauthorized)
The problem (I think) is that when I get routed to the home page the header is missing the token. But as you can see from the console log the token is available in app.service.ts.
When I reload the page I do have the token set in the header and everything works:
Any ideas on how to expose the token to the header after the redirect?
//EDIT
For some reason I do get the JWT token when I set the header directly in the function:
return this.http.get<T[]>('/api/movies/all', {headers: new HttpHeaders().set('Authorization', 'Bearer ' + this.authenticationService.getToken())});
Instead of calling it like this:
headers = new HttpHeaders().set('Authorization', 'Bearer ' + this.authenticationService.getToken());
return this.http.get('/api/movies/' + id, {headers: this.headers});

New Session/Cookie for Each User in Express

I'm using express to make API calls to a e-commerce platform. The API uses sessions to handle the persistent data needed for user tasks, like account and cart records. Cart and account details are attached to sessions (and the cookies that the sessionID is stored in), so when I log in with User1 and create a cart with items, and then log out, the cart persists. However, when logging in with User2, they inherit the cart of User1 because it's attached to the session.
EDIT/UPDATE
Main app.js:
var nodemailer = require("nodemailer"),
request = require("superagent"),
flash = require("connect-flash"),
bodyParser = require("body-parser"),
session = require("express-session"),
cookieParser = require("cookie-parser"),
methodOverride = require("method-override"),
Schema = require("schema-client"),
express = require("express"),
nodeuuid = require("uuid"),
cors = require("cors"),
app = express();
app.use(session({
name: "X-Session",
secret: "randomstring",
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 60*60*1000,
secure: false
}
}));
app.use(bodyParser.urlencoded({extended:false}))
.use(cookieParser())
.set("view engine", "ejs")
.use(express.static(__dirname + "/public"))
.use(methodOverride("_method"))
.use(flash())
var client = new Schema.Client("clientname", 'privateKeyhere');
var SchemaAPI = "https://clientname:privatekey#api.schema.io";
app.use(function(req, res, next){
res.locals.success = req.flash("success");
res.locals.errors = req.flash("error");
res.locals.account = req.session.account;
res.locals.session = req.session;
res.locals.cart = req.session.cart;
if(req.session.account_id){
client.get("/accounts/{id}", {
id: req.session.account_id
}, function(err, account){
if(account){
req.account = account;
res.locals.account = account;
};
next();
});
} else {
next();
}
});
Login Route:
app.post("/login", function(req, res, next) {
request
.post('http://localhost:3001/v1/account/login')
.set('X-Session', req.session.id)
.set('Accept', 'application/json')
.send({
email: req.body.email,
password: req.body.password
})
.end(function (error, account){
if(account){
account = account.body;
req.session.account = account;
console.log(account.name + " - " + account.id);
console.log(req.sessionID);
req.flash("success", "Logged in, " + account.email + ".");
res.redirect("/index");
} else if(account == null){
req.flash("error", "Your username or password is incorrect. Try again or <a href='/register'> sign up here</a>");
res.redirect("login");
} else {
console.log(error);
res.redirect('login');
}
});
});
All my other app routes have that "X-Session" header being passed with each request.
How can I create one session for each user such that when they log in, their session is retrieved, along with any cart information associated with their session? I'm using express-session to generate a sessionID, and then passing that ID to the API. Thanks in advance for your help.

Express.js 3 creates new sessions with each XHR request from Backbone.js

I have a Backbone app that queries my Express server with a un/pw, authenticates, then sends the account info (from MongoDB) along with the new sessionID back to the client. When i need more data, i attach the session id to the .fetch() options. However, Express creates a new session, even though my session was stored in Redis successfully.
Here is the middleware that checks if the client is trying to work with my api
var _restrictApi = function(req, res, next) {
if (req.url.match(/api/)) {
res.xhrAuthValid = req.param('sessionId') == req.sessionID;
if (res.xhrAuthValid || (req.method=='GET' && req.url.match(/api\/account/))) {
console.log('API access granted', req.url);
console.dir(req.session);
next();
} else {
console.log('API access BLOCKED', req.url);
console.log(req.param('sessionId'), req.sessionID);
console.dir(req.session);
res.send(403, 'Forbidden');
}
} else {
next();
}
};
My Backbone app makes a few .fetch() calls upon loading. First, log-in, then grab events for the user. Here is the Express server console log:
API access granted /api/account?email=test%40gmail.com&password=somepw
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
_checkAccount test#gmail.com
pw matches
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
account:
{ _id: 500471eb8bfff124ce984917,
dtAdd: '2012-07-16T20:07:58.671Z',
email: 'test#gmail.com',
pwHash: '$2a$10$2KJXrZeAGW58Kp9JQDL9B.K2Fvu2oE3oqWKRl55o8MeXGHA/zCBE.',
sessionId: 'iqYjOA7CeQHny9cm8zOWERjv' } }
API access BLOCKED /api/events?accountId=500471eb8bfff124ce984917&sessionId=iqYjOA7CeQHny9cm8zOWERjv
iqYjOA7CeQHny9cm8zOWERjv rsSXKtzXNNiq8x3+pUN9JXWF
{ cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true } }
Create a connect.sid by signing the sessionID:
cookie = require('cookie-signature')
res.send('connect.sid=s%3A'+cookie.sign(req.sessionID, req.secret))
Store it in local storage and set the Cookie header for each AJAX request:
r.setRequestHeader('Cookie', window.localStorage['connect.sid'])
That works.

Resources