I am new to Auth0 and trying to implement it in my regular express web application. I need to protect/validate the user before they access some of my endpoints. My understanding is that i can do this with the JWT that is returned from the login callback. I have gotten that far, but when I login, it redirects, and I'm unsure of how to pass in the access token/store it securely on the client side.
this is what my callback endpoint looks like after logging in. It returns the authorization code but I am lost from here.
https://auth0.com/docs/api-auth/tutorials/authorization-code-grant
I return this on login:
/callback?code=oi9-ZTieXo0hYL6A&state=sMJAUK4QVs7jziJ7lXvwmGKF
// Perform the final stage of authentication and redirect to previously requested URL or '/user'
router.get('/callback', function (req, res, next) {
passport.authenticate('auth0', function (err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, function (err) {
if (err) { return next(err); }
const returnTo = req.session.returnTo;
delete req.session.returnTo;
res.redirect('/user);
});
})(req, res, next);
});
where do i go from here?
Auth0 does not recommend storing tokens in browser storage (session/local storage). For client side applications, tokens should be short lived and renewed when necessary via silent authentication (renewed via a cookie session with the auth server in a hidded iframe).
This is outlined here:
https://auth0.com/docs/security/store-tokens
If you have a backend, then handle the tokens there, if you are using a SPA + API then use the strategy outlined in the link.
Related
I have a button in my HTML body, which is supposed to load a user's google contacts when a user clicks the button. I've registered for the required credentials and authorization via Google Cloud Platform, but for some reason, the javascript code I have is not working and clicking the button in demo mode in Visual studio does not open a new window asking me to login into my gmail account, permission for the website to access my contacts, etc. Any help in getting the API to work on my website will be much appreciated.
<button id="google-button" onclick="return auth()" style="color:white;background-color:royalblue;margin:20px;padding:5px 10px;border-style:solid;font-weight:bold">Share to all of your Google contacts</button>
<script>
function auth() {
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/contacts.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Tasks API.
authorize(JSON.parse(content), listConnectionNames);
});
function authorize(credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function listConnectionNames(auth) {
const service = google.people({ version: 'v1', auth });
service.people.connections.list({
resourceName: 'people/me',
pageSize: 10,
personFields: 'names,emailAddresses',
}, (err, res) => {
if (err) return console.error('The API returned an error: ' + err);
const connections = res.data.connections;
if (connections) {
console.log('Connections:');
connections.forEach((person) => {
if (person.names && person.names.length > 0) {
console.log(person.names[0].displayName);
} else {
console.log('No display name found for connection.');
}
});
} else {
console.log('No connections found.');
}
});
}
}
</script>
The code in your <script> tag is server-side Node.js code, not client-side JavaScript. It will not function in the browser because:
require('fs') imports the filesystem module, but no such thing exists outside of Node.js.
readline and googleapis are also Node-specific modules, so they have no meaning in client-side JS and will probably throw errors if require hasn't already.
fs.readFile(...) attempts to use the fs module (see above) to read a file at a certain path, but client-side JavaScript doesn't have access to the filesystem.
Fundamentally, OAuth negotiation should be handled on the server, not on the client. Typically, a privileged request will use data from your database, which it cannot do if the token is stored on the client.
It seems like the main problem here is confusion about what OAuth is and how it works. Here's a simplified step-by-step walkthrough of the process:
The user logs into an external service from their client.
The external service generates a code (a token) and sends it to the client.
The client receives the token (in a callback, hash param, etc.) and sends it to your server. Most often the external service will simply redirect the client to a URL on your server with the token in the query string, allowing you to grab it out of the request.
Your server stores the token for a specific user account in your database.
For privileged actions, your server sends the request to the external service and includes the token.
The server receives your request with the token and performs an action on the user's behalf.
When the external service receives a request with a token, it looks up that token and sees that it belongs to a specific user. Because that user must have logged in and authorized your app in order to create the token, the service knows that it should proceed with the action.
OAuth tokens may be permanent, but much more often they will expire after a set period of time and have to be regenerated. This means that you should never be using the token as a primary key to identify a user. As for how to regenerate an expired token, the exact details vary by provider. The service you're using (Google, in this case) will have more information on how their auth flow works, and how refreshing should be handled.
I have some routes in my Node.js API sending data from a MongoDB database to an Angular 4 frontend.
Example:
Node.js route:
router.get('/api/articles', (req, res) => {
Article.find({}, (err, articles) => {
if(err) return res.status(500).send("Something went wrong");
res.status(200).send(articles);
});
});
Angular 4 service function:
getArticles() {
return this.http.get('http://localhost:3000/api/articles')
.map(res => res.json()).subscribe(res => this.articles = res);
}
The question is, how do I protect my Node.js API routes from browser access? When I go to http://localhost:3000/api/articles I can see all my articles in json format.
This is not a security measure, just a way to filter the request. For security use other mechanisms like JWT or similar.
If the angular app is controlled by you then send a special header like X-Requested-With:XMLHttpRequest (chrome sends it by default for AJAX calls) and before responding check for the presence of this header.
If you are really particular about exposing the endpoint to a special case use a unique header may be X-Request-App: MyNgApp and filter for it.
You can't really unless you are willing to implement some sort of authentication — i.e your angular user will need to sign into the api.
You can make it less convenient. For example, simply switching your route to accept POST request instead of GET requests will stop browsers from seeing it easily. It will still be visible in dev tool or curl.
Alternatively you can set a header with your angular request that you look for in your express handler, but that seems like a lot of work for only the appearance of security.
Best method is to implement an authentication token system. You can start with a static token(Later you can implement dynamic token with authorisation).
Token is just a string to ensure the request is authenticated.
Node.js route:
router.get('/api/articles', (req, res) => {
let token = url.parse(req.url,true).query.token; //Parse GET param from URL
if("mytoken" == token){ // Validate Token
Article.find({}, (err, articles) => {
if(err) return res.status(500).send("Something went wrong");
res.status(200).send(articles);
});
}else {
res.status(401).send("Error:Invalid Token"); //Send Error message
}
});
Angular 4 service function:
getArticles() {
return this.http.get('http://localhost:3000/api/articles?token=mytoken') // Add token when making call
.map(res => res.json()).subscribe(res => this.articles = res);
}
With Express, you can use route handlers to allow or deny access to your endpoints. This method is used by Passport authentication middleware (which you can use for this, by the way).
function isAccessGranted (req, res, next) {
// Here your authorization logic (jwt, OAuth, custom connection logic...)
if (!isGranted) return res.status(401).end()
next()
}
router.get('/api/articles', isAccessGranted, (req, res) => {
//...
})
Or make it more generic for all your routes:
app.use('*', isAccessGranted)
I'm currently working on a angular + sails project. I'm using json web tokens for auth. It works fine but I wanna set a new token for every validated request that my angular app does.
This is my auth policy
passport.authenticate('jwt', function (error, user, info) {
if (error) return res.serverError(error);
if (!user)
return res.send({
message: info.message,
code: info.code,
tokenError: info.name
});
// The token is ok past this line
// I check the user again
User.findOne({ email: user.email }, function (err, thisUser) {
if (err) { return res.send(err); }
if (!thisUser) {
// send a bad response
}
req.user = user;
// This is the new token that I wanna send to the frontend
var newToken = AuthService.createToken(thisUser);
next();
});
})(req, res);
With this policy I can create the new token, but then I would need a way to include this token in every response, this Is the point where I'm stuck.
I gues I could do it manually in every controller action, but this is want I want to avoid
The best way to standardize your responses in Sails is to use the custom responses feature. In short, instead of calling res.send() or res.json() in your controller actions, call res.ok() instead, and then customize the api/responses/ok.js file that is generated with every new Sails app. This is the same response that Sails blueprints use as well!
In your case, you'd want to save the token onto the request object (e.g. req.token) in your policy code, then use that property in your logic inside of ok.js.
I have a NodeJS Express app that uses express-session. This works great, as long as session cookies are supported.
Unfortunately it also needs to work with a PhoneGap app that does not support cookies of any kind.
I am wondering: Is it possible to get an express session and access the data in that session, using the sessionID?
I am thinking I could append the sessionID as a querystring parameter for every request sent by the PhoneGap app like so:
https://endpoint.com/dostuff?sessionID=whatever
But I don't know how to tell express to retrieve the session.
You can certainly create an express route/middleware that tricks express-session that the incoming request contains the session cookie. Place something like this before the session middleware:
app.use(function getSessionViaQuerystring(req, res, next) {
var sessionId = req.query.sessionId;
if (!sessionId) return res.send(401); // Or whatever
// Trick the session middleware that you have the cookie;
// Make sure you configure the cookie name, and set 'secure' to false
// in https://github.com/expressjs/session#cookie-options
req.cookies['connect.sid'] = req.query.sessionId;
next();
});
Seems like req.cookies isn't accessible in my case. Here's another solution that recreates the session using the 'x-connect.sid' header (you may use any name or even a query param if you like).
Put this middleware after the session middleware
// FIRST you set up your default session like: app.use(session(options));
// THEN you recreate it using your/custom session ID
app.use(function(req, res, next){
var sessionId = req.header('x-connect.sid');
function makeNew(next){
if (req.sessionStore){
req.sessionStore.get(sessionId, function(err, session){
if (err){
console.error("error while restoring a session by id", err);
}
if (session){
req.sessionStore.createSession(req, session);
}
next();
});
} else {
console.error("req.sessionStore isn't available");
next();
}
}
if (sessionId) {
if (req.session){
req.session.destroy(function(err){
if (err) {
console.error('error while destroying initial session', err);
}
makeNew(next);
});
} else {
makeNew(next);
}
} else {
next();
}
});
I'm trying to build a simple application with parse.com as my user manager.
I would like to make a login call to parse.com from my client side, and call my node.js server with the user's session token (I'll add it as a cookie). In the server side, I'll validate the session (using https://parse.com/docs/rest#users-validating) and allow access only if the session is valid.
For example (in my server):
app.get('/api', function(req, res, next) {
var token = getTokenFromRequest(req);
if(tokenIsValid(token)) {
next();
} else { // Redirect... }
});
app.get('/api/doSomething', function(req, res) {
// Do something....
});
the tokenIsValid(token) function should be implemented using https://parse.com/docs/rest#users-validating.
However, it seems that the REST API user validation returns the user even if the user is logged out (expected to return 'invalid session').
Is this a bug in the REST API user validation? What am I doing wrong? Is there a better way for doing that?
Thanks!
Via REST there's no concept of sessions really. REST calls are meant to be stateless meaning that the (current) user at /me will be serialized from the token provided. If the token is associated to a user it will return the JSON representation of that user otherwise in returns an error.
One way or another that call is asynchronous so you can't really use it in and if statement.
You can do:
app.get('/api', function(req, res, next) {
var token = getTokenFromRequest(req);
serializeUserFromToken(token,function(err,parseResponse) {
if(err) return next(err)
if(parseResponse.code && parseResponse.code === 101){
// called to parse succedded but the token is not valid
return next(parseResponse);
}
// parseResponse is the current User.
next();
});
});
Where serializeUserFromToken makes a request to Parse with the token in the X-Parse-Session-Token header field.