Is there any way to capture bad session token requests on a remote parse-server app?
Unfortunately, verbose logs for bad session tokens only display a very not verbose error message that an invalid session token was used, whereas all other requests will display the full headers used to make a request. I need to get to those headers so I can identify the x-parse-session-token being sent.
I've discovered I can add some middleware doing something like this:
var myMiddleware = function (req, res, next) {
// extract request headers
next()
}
app.use(myMiddleware)
But I wouldn't yet know if the session token was valid or not at that point, and I don't think it'd be efficient to set up a whole extra middleware checking the validity of every session token that gets passed in.
Any tips on how I could get access to these x-parse-session-token headers for requests that are failing due to bad session tokens would be greatly appreciated.
One hacky way would be to override in your middleware the req.json call.
const myMiddleware = function (req, res, next) {
const json = res.json;
res.json = function(object) {
if (object.code == Parse.Error.INVALID_SESSION_TOKEN) {
// get the session token
const token = req.headers['x-parse-session-token'];
// Invalid token, do something
}
// Forward the response
json.call(res, object);
}
next()
}
server = new ParseServer({
applicationId: YOUR_APP_ID,
/* more options */
middleware: myMiddleware
});
// continue initialization here
This should do the trick what do you think?
Related
On a server side I have 2 middlewares - protect (is logged in?) and restrictTo (checks user's role). These middlewares stop users or guests from performing certain actions if they are not allowed to
exports.protect = catchAsync(async (req, res, next) => {
let token;
if (
req.headers.authorization && req.headers.authorization.startsWith("Bearer")
) {
token = req.headers.authorization.split(" ")[1];
}
if (!token) {
return next(new AppError("You are not signed in!", 401));
}
const decodedToken = await promisify(jwt.verify)(
token,
process.env.JWT_SECRET
);
const currentUser = await User.findById(decodedToken.id);
if (!currentUser) {
return next(new AppError("User with such token no longer exists"));
}
req.user = currentUser;
next();
});
exports.restrictTo = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new AppError("No access", 403));
}
next();
};
};
But how do I protect routes on a client side? If I'm not allowed to post a new note then I should be stopped from going to a /newnote page so I can't see and fill the form.
JWT token is stored in cookies with httpOnly flag. So I can't access the token from a Vue router. Store a user's role in Vuex? Then how do I synchronize the token state in cookies and in Vuex? If my token has been destroyed on a server side I still can have it in Vuex until I send a request to a protected endpoint.
Should I request a special auth endpoint for protected routes to check my current role using beforeEach?
Basically, you should add two things:
store the current authenticated user. By default, authUser is null. When someone logs in, authUser is an object with the user’s data. you can store this in Vuex, localStorage, etc.
create an interceptor/middleware in whatever library you are using for your api requests. If at some point you get a 401/403, it means that the current user’s session expired, or he is trying to access a protected area he shouldnt be looking at. Either way, reset the local authUser to null and redirect to login.
In Spa/mobile you dont have to worry too much about this as long as your backend is properly secured. If your authUser logic is correct, then only users with bad intentions will try to reach protected areas, whereas normal users will play by the rules and never hit a page they arent supposed to with their current privileges (assuming the UI is wired up correctly…).
There are a number of solutions to this:
use the build-in dialog provided by esri/IdentityManager (https://developers.arcgis.com/javascript/3/jsapi/identitymanagerbase-amd.html)
use a server-side proxy (https://github.com/Esri/resource-proxy)
use the identity manager initialize() method (https://developers.arcgis.com/javascript/3/jsapi/identitymanagerbase-amd.html#initialize)
But there what is missing is the ability to hook into the request for a token. I am working with ArcGISDynamicMapServiceLayer and there is no way to know if the server return a 498/499, and no way to update the url to update the token.
I started hacking around in the API to try to hook into various events with no real promise of success. What seems to be missing:
a way to detect when a token is needed
a way to update the token
Closes I came up with is listening for "dialog-create" but there is no way to disable the dialog apart from throwing an exception, which disables the layer.
I tried replacing the "_createLoginDialog" method and returning {open: true} as a trick to pause the layers until I had a token ready but since there is no way to update the layer endpoint I did not pursue this hack. It seems the only way this might work is to use the initialize() method on the identity manager.
Does anyone have knowledge of options beyond what I have outlined?
EDIT: The goal is to provide a single-sign-on experience to users of our product.
"User" is already signed in to our application
"User" wishes to access a secure ESRI ArcGIS Server MapServer or FeatureServer services from the ESRI JSAPI
"User" is prompted for user name and password
The desired flow is to acquire a token on the users behalf using a RESTful services in our product and return the appropriate token that will allow the "User" to access the secure services without being prompted.
I do not wish to use a proxy because I do not want all that traffic routed through the proxy.
I do not wish to use initialize() because it is complicated and not clear how that works apart for re-hydrating the credentials.
I do wish for an API that simply allows me to set the token on any layer services that report a 499 (missing token) or 498 (invalid token), but I cannot find any such API. The solution I am focusing on hinges on being able to update the url of an ArcGISImageServiceLayer instance with a new token.
This answer lacks in satisfaction but delivers on my requirements. I will start with the code (client-side typescript):
class TokenProxy {
private tokenAssuranceHash = {} as Dictionary<Promise<{ token: string, expiration: string }>>;
private service = new TokenService();
private timeoutHandle = 0;
watchLayer(esriLayer: ArcGISDynamicMapServiceLayer) {
setInterval(async () => {
const key = esriLayer._url.path;
const token = await this.tokenAssurance(key);
esriLayer._url.query.token = token;
}, 5000);
}
updateRefreshInterval(ticks: number) {
clearTimeout(this.timeoutHandle);
this.timeoutHandle = setTimeout(() => {
Object.keys(this.tokenAssuranceHash).forEach(url => {
this.tokenAssuranceHash[url] = this.service.getMapToken({serviceUrl: url});
});
this.updateRefreshInterval(ticks);
}, ticks);
}
async tokenAssurance(url: string) {
if (!this.tokenAssuranceHash[url]) {
this.tokenAssuranceHash[url] = this.service.getMapToken({serviceUrl: url});
}
try {
const response = await this.tokenAssuranceHash[url];
await this.recomputeRefreshInterval();
return response.token;
} catch (ex) {
console.error(ex, "could not acquire token");
return null;
}
}
async recomputeRefreshInterval() {
const keys = Object.keys(this.tokenAssuranceHash);
if (!keys.length) return;
const values = keys.map(k => this.tokenAssuranceHash[k]);
const tokens = await Promise.all(values);
const min = Math.min(...tokens.map(t => new Date(t.expiration).getTime()));
if (Number.isNaN(min)) return; // error occured, do not update the refresh interval
const nextRefreshInTicks = min - new Date().getTime();
this.updateRefreshInterval(0.90 * nextRefreshInTicks);
}
}
And highlight the hack that makes it work:
const key = esriLayer._url.path;
const token = await this.tokenAssurance(key);
esriLayer._url.query.token = token;
The "_url" is a hidden/private model that I should not be using to update the token but it works.
I run an backend and a frontend both served by express the backend on port 8080 and the frontend on port 80.
/api/route1 returns 200ok with json
/api/route2 returns 200ok with json
So the app works fine fetching these routes. Now to the thing I need your help with. I have added next-auth so in the frontend I can
const [ session, loading ] = useSession();
to do something like
{!session && <p>You are not logged in</p>}
which works but what I haven't figured out is how to protect the routes to the API. I want to protect route1 and route2 in both frontend and backend. I guess when I'm logged in a need to pass a token down to the API but how can I have these 2 talking to each other
/api/route1 returns 200ok with json
/api/route2 returns 200ok with json
Remember I run the backend and frontend separately because my production build is in docker that's why.
You can find an example of this in the next-auth-example project
// pages/api/examples/protected.js
import { getSession } from 'next-auth/client'
export default async (req, res) => {
const session = await getSession({ req })
if (session) {
res.send({ content: 'This is protected content. You can access this content because you are signed in.' })
} else {
res.send({ error: 'You must be sign in to view the protected content on this page.' })
}
}
If a session object exists (i.e. is not null) then it means they either have a valid session token (if using database sessions) or a valid signed JSON Web Token (if using JWT session).
In both cases the session token is checked to make sure it is valid and has not expired.
The request object req is passed through to getSession() call when used in this way so that the cookie containing the session token can be inspected and validated.
The way that you could handle protected routes within Node is by using middleware.
So lets say that you have a route for adding employees salary in database, so obviously such a route needs someone that is and authenticated admin right?
So you could make a middleware function like the simple one below
const validateAdminCookie = (req, res, next)=>{
//Here you then write all your logic on how you validate admin
//Now you will have conditonals here that:
if (!validatedCookie){
return res.status(400).json({msg:'Not authorized'})
}
next();
}
So now that function is what you will pass within your route so that is gets executed first and when user is valid authenticated admin then the next() will push down that user to the main route that they were trying to hit else if not authenticated then the get back a message that they are not authenticated.
Now how you pass this middleware is like this below:
router.post('/api/admin-update-salaries',validateAdminCookie, (req, res)=>{
//Now that **validateAdminCookie** will execute first and if all
//checks out then user will be pushed down to the main part
//that is this route here
})
I'm working on a MEAN application with authentication using JSON web tokens. basically on every request, I am checking to see if user has a valid token. if so they can go through to the route, otherwise they are returned to login page.
I want to make certain routes /admin/etc... only accessible to logged in users who are also admin. I have set up an isAdmin flag in mongo. I am new to nodejs and wondering what is the best way to check this. Do I do it on the angular side in routes? Or can I somehow create permission-based tokens on authentication? For reference, I am following the code from the MEAN Machine book, in particular here -
https://github.com/scotch-io/mean-machine-code/tree/master/17-user-crm
First, authorization decisions must be done on the server side. Doing it on the client side in Angular.js as you suggested is also a good idea, but this is only for the purpose of improving the user's experience, for example not showing the user a link to something they don't have access to.
With JWTs, you can embed claims about the user inside the token, like this:
var jwt = require('jsonwebtoken');
var token = jwt.sign({ role: 'admin' }, 'your_secret');
To map permissions to express routes, you can use connect-roles to build clean and readable authorization middleware functions. Suppose for example your JWT is sent in the HTTP header and you have the following (naive) authorization middleware:
// Naive authentication middleware, just for demonstration
// Assumes you're issuing JWTs somehow and the client is including them in headers
// Like this: Authorization: JWT {token}
app.use(function(req, res, next) {
var token = req.headers.authorization.replace(/^JWT /, '');
jwt.verify(token, 'your_secret', function(err, decoded) {
if(err) {
next(err);
} else {
req.user = decoded;
next();
}
});
})
With that, you can enforce your authorization policy on routes, like this:
var ConnectRoles = require('connect-roles');
var user = new ConnectRoles();
user.use('admin', function(req) {
return req.user && req.user.role === 'admin';
})
app.get('/admin', user.is('admin'), function(req, res, next) {
res.end();
})
Note that there are much better options for issuing & validating JWTs, like express-jwt, or using passport in conjunction with passort-jwt
I am creating a keystone project and I need to provide allow or not allow users to access using the keystone signin. However, I found that keystoneJS sends a form data with email, password and csrf. This csrf is given to the user when he access to the login page.
Nevertheless, what I need to do is to comunicate externally to login the user, by using an API. How can I generate the _csrf? Is there another way then generate two requests?
Thanks
#Sericaia, you didn't include any code or specifics on how you intend to implement your login page, so my answer will be a little vague.
Keystone has an internal API for handling CSRF token creation and validation. I don't think it's documented, but here's a gist of how it works.
In your route handler you can create a CSRF token key/value pair that you can then inject into your view locals and then use in your view template. You can do it manually like this.
app.get('/login', function (req, res) {
var keystone = require('keystone');
var csrfTokenKey = keystone.security.csrf.TOKEN_KEY;
var csrfTokenValue = keystone.security.csrf.getToken(req, res);
res.render('login', {
csrfTokenKey: csrfTokenKey,
csrfTokenValue: csrfTokenValue
});
});
Or you can use provided middleware.
// the middleware will automatically inject the CSRF token
// into res.locals[keystone.security.csrf.LOCAL_KEY]
app.get('/login', keystone.security.csrf.middleware.init, function(req, res) {
...
});
You can also validate the CSRF token received from the client. You can do it manually as follows:
app.post('/login', function(req, res) {
if (keystone.security.csrf.validate(req)) {
// CSRF is valid
...
} else {
// CSRF is not valid
...
}
});
Or you can use the provided middleware.
// the middleware will return 403 status with "CSRF token mismatch"
// of there's a error validating the CSRF token received
app.post('/login', keystone.security.csrf.middleware.validate, function(req, res) {
...
});
Hope this helps.