I have a simple app I'm building using Play + AngularJS that requires authentication before most routes can be accessed. The login flow includes a "remember me" feature that stores a session ID in to the browser local storage and gets mapped to a valid authorized database session entry on the server side any time a user returns to the app.
The problem I'm having is that I do the session checking (extract cookie & compare against server) in the run() function of the module:
.run(function ($rootScope, $http, $cookieStore, $location) {
// <snip>
// check if there is already a session?
var sessionId = window.localStorage["session.id"];
if (sessionId == null) {
sessionId = $cookieStore.get("session.id");
}
if (sessionId != null) {
$http.get("/sessions/" + sessionId)
.success(function (data) {
$http.defaults.headers.common['X-Session-ID'] = data.id;
$cookieStore.put("session.id", data.id);
$rootScope.user = data.user;
})
.error(function () {
// remove the cookie, since it's dead
$cookieStore.remove("session.id");
window.localStorage.removeItem("session.id");
$location.path("/login");
});
} else {
if ($location.path() != "/login" && $location.path() != "/signup") {
$location.path("/login");
}
}
});
The problem is that this function executes an AJAX call and I don't know if the session is valid until it completes. However, the controller that loads (via the route selected by $routeProvider) can fire away another AJAX call that often kicks off before the other one finishes, resulting in a race condition and the initial request getting a 401 response code.
So my question is: how can I force run (with its associated $http call) to complete before any other part of the app runs? I have tried using $q/promise here and it doesn't seem to make a difference (perhaps run functions don't honor promises). I've been advisor to use resolve feature in $routeProvider but I don't know exactly what to do and I'm not super execited about having to put that in for every route anyway.
I assume this is a pretty common use case and it gets solved every day. Hopefully someone can give me some direction with my code, or share their approaches for "remember me" and AngularJS.
You need to manual bootstrap your app after you get session from server.It's easy if you use jQuery for example you can do, or even without jQuery you can use injector to access $http before bootstrapping
$.get(server,function(){
//success , set variable.
}).fail(function (){
//failed :( redirect to login or set session to false etc... null
})
.always(function(){
//alwyas bootstrap in both case and set result as a constant or variable Angular.module('app').variable('session',sessionResult);
});
I'm on phone right now, but this should give u the idea
Related
Is it possible to have protected routes in the Remix.run React framework, so that only admin users get the protected components, while regular users don't get the protected components at all as part of the JS bundle sent to the browser?
Also, this may require a form of code splitting on the front end side. Is code splitting supported in Remix.run?
this is a code snippet from a sample app I wrote, this is the home page and can only be accessed if the user is authenticated.
the redirect(`/login?${searchParams}`) will redirect if the user isn't authenticated
// Loaders provide data to components and are only ever called on the server, so
// you can connect to a database or run any server side code you want right next
// to the component that renders it.
// https://remix.run/api/conventions#loader
export let loader = async ({ request }) => {
const redirectTo = new URL(request.url).pathname;
let session = await getSession(request.headers.get("Cookie"));
// if there is no access token in the header then
// the user is not authenticated, go to login
if (!session.has("access_token")) {
let searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
throw redirect(`/login?${searchParams}`);
} else {
// otherwise execute the query for the page, but first get token
const { user, error: sessionErr } = await supabaseClient.auth.api.getUser(
session.get("access_token")
);
// if no error then get then set authenticated session
// to match the user associated with the access_token
if (!sessionErr) {
// activate the session with the auth_token
supabaseClient.auth.setAuth(session.get("access_token"));
// now query the data you want from supabase
const { data: chargers, error } = await supabaseClient
.from("chargers")
.select("*");
// return data and any potential errors alont with user
return { chargers, error, user };
} else {
return { error: sessionErr };
}
}
};
You can protect routes by authorizing the user inside the loader of the Route, there you could decide to redirect it somewhere else or send a flag as part of the loader data so the UI can hide/show components based on it.
For the code splitting, Remix does it at the route level, but it doesn't support server-side code-splitting out of the box, you may be able to support it with react-loadable.
I hope it has, but not. Below is the official answer.
https://remix.run/docs/en/v1/pages/faq#how-can-i-have-a-parent-route-loader-validate-the-user-and-protect-all-child-routes
You can't 😅. During a client side transition, to make your app as speedy as possible, Remix will call all of your loaders in parallel, in separate fetch requests. Each one of them needs to have its own authentication check.
This is probably not different than what you were doing before Remix, it might just be more obvious now. Outside of Remix, when you make multiple fetches to your "API Routes", each of those endpoints needs to validate the user session. In other words, Remix route loaders are their own "API Route" and must be treated as such.
We recommend you create a function that validates the user session that can be added to any routes that require it.
I'm trying to push one inputless TV screen dashboard (using chromecast) with azure authentication in nodejs (working fine without auth so far)
My best move (?) is using ms-rest-azure package allowing to perform initial authentication from another device with https://aka.ms/devicelogin & a code
However, is there a clean way to retrieve this code and make it available outside the console ? I can't find reference or callback.
My fallback scenario would be to intercept process.stdout.write but feels like dirty.
There is an options object you can pass to interactiveLogin. One option is "userCodeResponseLogger", which should be a function, eg
let options = {"userCodeResponseLogger":(msg)=>{
console.log("I have the message",msg)
}
}
msRestAzure.interactiveLogin(options).then((credentials) => {
// doing authentication stuff
});
Note you'll still need to parse the msg to extract the code.
had to go forward on this issue and finally ends up by intercepting process.stdout.write with a better implementation then mine : https://gist.github.com/pguillory/729616/32aa9dd5b5881f6f2719db835424a7cb96dfdfd6
function auth() {
hook_stdout(function(std) {
var matches = / the code (.*) to /.exec(std);
if(matches !== null && matches.length >=2) {
var code = matches[1];
// doing something with the code
unhook();
}
});
msRestAzure.interactiveLogin().then((credentials) => {
// doing authentication stuff
});
}
The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:
How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?
Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?
I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.
By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.
By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.
exports.customer_profile = function (req, res) {
var token = req.headers.token;
var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
var decoded = jwt.verify(token, public_key);
var sql = 'SELECT * FROM customer WHERE username = "' + decoded.sub + '"';
util.conn.query(sql, function (err, rows) {
if (!err) {
for (var i = 0; i < rows.length; i++) {
res.render('customer_profile', {customer_profile: rows[i]});
break;
}
}
});
};
I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.
So far what I was able to figure out is the following:
Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
// Get current user's ID Token
firebase.auth().currentUser.getIdToken()
.then(token => {
// Make a fetch request to 'path'
return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
method: 'GET',
headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
});
})
.then(response => {
// As noted below, this part I haven't solved yet.
// TODO: Open response as new webpage instead of displaying as data in existing one
return response.text();
})
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
}
Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup).
// getLocale - language negotiation.
// getContext - auth token verification if it is available and appends it to Request object for convenience
app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);
// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
if(!idToken) {
return next(); // Passes to next middleware if no token, terminates further execution
}
admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
.then(token => {
req.decoded_token = token; // Append token to Request object for convenience in further middleware
return next(); // Pass on further
})
.catch(error => {
console.log('Request not authorized', 401, error)
return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
});
}
Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user
router.get('/console', middleware.getTranslation('console'), (req, res) => {
if(req.decoded_token) { // if token was verified successfully and is appended to req
res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
} else {
res.status(401).send('Not authorized'); // else send 401 to user
}
});
As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access
What I have not solved yet:
As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.
Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet
I'm currently develop an application with Sails.JS.
I want to count the number of online users and update it once they login/ logout or there session expire, but I don't know how to implement something like session destroyed event and can't update the number of online user whenever a session is expired without user logging out.
As other said above, there is no such events in the default session implementation, Sails session are close to ExpressJs Session, i recommend you to read this article about ExpressJs Sessions :
http://expressjs-book.com/forums/topic/express-js-sessions-a-detailed-tutorial/
Then one idea in order to achieve what you want could be to use a store and query inside of it.
Did you though about other solutions such as using socket.io (built in sails) and adding your users into a channel upon login and then simply counting user inside your channel ?
You can wrap the session.destroy() function like so:
var destroyWrapper = buildDestroyWrapper(function(req){
//do stuff after req.destroy was called
});
function buildDestroyWrapper(afterDestroy){
return function(req){
req.destroy();
afterDestroy(req);
};
}
//later, in your controller
function controllerAction(req,res,next){
destroyWrapper(req);
}
this method allows you to handle destruction differently, depending on what callback you pass to buildDestroyWrapper. For example:
var logAfterDestroy = buildDestroyWrapper(function(req){
console.log("session destroyed");
});
var killAfterDestroy = buildDestroyWrapper(function(req){
process.kill();
});
function buildDestroyWrapper(afterDestroy){
return function(req){
req.destroy();
afterDestroy(req);
};
}
//later, in your controller
function logoutAction(req,res,next){
logAfterDestroy(req);
}
function killAppAction(req,res,next){
killAfterDestroy(req);
}
I'm putting together a basic project admin/management site and decided to finally learn to use node/express/monk/jade/redis, the works. Everything was going fine but I've run into a problem trying to get data passed between the route handler in index.js and the jade template file.
in index.js
exports.auth = function( db )
{
return function( req, res )
{
var userName = req.body.username,
userPassword = req.body.password,
authenticated = false;
// check credentials code
// ...
if (authenticated)
{
// set some session stuff
res.redirect( "home" ); // good to go
}
else
{
res.locals.err = "Authentication error";
res.redirect( "login" ); // show err on login page
}
}
}
in login.jade
- if (typeof( locals.err ) !== 'undefined' ) {
p.errormsg #{ locals.err }
- }
Iterating over locals in the jade template it doesn't show an entry for err. Does res.redirect() wipe out the err entry I made in index.js? Or am I actually dealing with two different objects (res.locals in index.js and locals in the jade template)?
My original approach was to use res.render( "login", { "err" : "Authentication err" } ) instead of redirecting, but I cannot figure out how to get the browser to show /login and not /auth when the error happens. I tried
res.location( "login" );
res.render( "login", { "err" : "Authentication err" });
but the browser still shows /auth.
The only other approach I found was using session data. The session object is available in both places and I can set/read the information from it as needed. The solution is inelegant though since the session info persists through reloads of the login page so the browser just keeps showing the error message for the original attempt rather than reloading/rendering a clean login page.
Any help is appreciated, and thanks in advance!
Yes - the redirect is returning a redirect to the client, which makes a separate request from the client. Your prior res.locals.err is long gone. You may want to read the doc on res.redirect().
Session data would be a sensible way to handle this unless you are a hardcore about statelessness. I am not sure why you find it inelegant. Why don't you reset that element of the session data after you render the next page?
There are different ways you can handle your issue about what the location bar shows if you search around for some javascript. Feels like a bit of kludge though.
Personally, I just have a /login path - called via GET it displays the login page, called via POST it authenticates, redirects if successful, or renders the login template with the error if the login is bad. No session data necessary.