Here is my backend code. It has 2 protected routes. Authorization Header is needed (Bearer token).
These routes allows you to download your photo and your cv.
I have no issue with my backend. It is working with postman and mobile applications. I think there is a limitation for frontend.
I do NOT want
I don't want to use fetch to download these files as blob. Because it doesn't allow to browser to show its progress bar.
I don't want to change my authentication method from Bearer token to cookie. I can already do this with cookie.
I wonder why Bearer token is more popular than cookie If I have to use cookie to make this happen.
Backend
// other imports .....
const path = require('path');
const fs = require('fs');
const express = require('express');
const app = express();
app.use((req, res, next) => {
try {
const token = req.get('Authorization');
if(!token) {
throw new Error('401');
}
const userId = getIdFromToken(token);
if(!userId) {
throw new Error('401');
}
res.locals.userId = userId;
next();
} catch (error) {
next(error);
}
})
app.get('/api/me/photo', (req, res) => {
const userId = res.locals.userId;
const imagePath = path.resolve(process.env.DISC_PATH, 'images', `${userId}.png`);
res.attachment('me.png');
res.set('Content-Type', 'image/png');
const stream = fs.createReadStream(imagePath);
res.pipe(stream);
})
app.get('/api/me/cv', (req, res) => {
const userId = res.locals.userId;
const pdfPath = path.resolve(process.env.DISC_PATH, 'cv', `${userId}.png`);
res.attachment('my-cv.pdf');
res.set('Content-Type', 'application/pdf');
const stream = fs.createReadStream(pdfPath);
res.pipe(stream);
})
...
// error handling, etc...
Frontend
<html>
<body>
<!-- How can I send the Authorization header here -->
<img src="/api/me/photo" />
<!-- How can I send the Authorization header here -->
Download My CV
<!-- How can I send the Authorization header here -->
<button id="btn"> Download My CV (V2) </button>
<script>
const btn = document.getElementById('btn');
btn.addEventListener('click', () => {
// How can I send the Authorization header here
window.open('/api/me/cv');
});
</script>
</body>
</html>
I wonder why Bearer token is more popular than cookie If I have to use cookie to make this happen.
One advantage of an Authorization: Bearer header over cookie-based authentication is precisely that the browser does not automatically include the header in a request to the given URL. So you cannot be tricked into clicking on a link that would trigger an authenticated request that you did not intend.
In other words: With Authorization: Bearer as authentication mechanism you are safe from "cross-site request forgery" (CSRF) attacks.
Backends that use cookie-based authentication have to implement extra countermeasures against CSRF.
For your problem, this means that the two don't go together. If you want the convenience of a "normal" browser request (with its progress bar), you expose the user to CSRF, unless you take said countermeasures. (This may not be a serious threat if the request only downloads something. But an attacker could at least measure the running time of the CV download request and deduce something from that.)
Possible workaround:
Implement an API (authenticated with Authorization: Bearer header) that generates a download URL which includes a short-lived token, and have the browser download the desired document with a normal request to that URL. The second request is technically unauthenticated, but the token makes the URL unguessable.
If the token is a JWT, you need not even store it on your server but can simply write the username and expiry time in it and sign it with the server's private key. If you prefer you can also put the token in a cookie rather than in the URL.
Related
There is a remote api and it needs hmac sha256 key which is generated with secret key and timestamp for each request.
This hmac header generation should not be seen on client code in Nuxt Project. And the requests has to be made on server side with the hmac header. For example I will send a login post request to the api. I will send password, email and hmac key as header. But this header generation code has to be hidden for the client side. And the post request should be done on server side. Not client side.
Api needs this hmac key header. To generate a hmac key, you will need a secret key which api provider gives us. you can see how hmac generation works on this link. We will put some data and timestamp. So api decrypts the key and checks the timestamp. if the timestamp delta is more than 20 second, the request will not accepted by api.
How to add this header to axios for only server side calls on axios requests and this generation script should not seen on client code ?
X-HMAC-TOKEN: 'hmac sha256 key generated with timestamp and secret key. and this generation script should not seen on client code'
Here is an example hmac authentication example on api side.
Seems like the only solution looks like using server middleware and probably express.
Here is a sample code which is shared on github. I just want to share the code here for everyone who has this question
First you should create server-middleware in /api folder /api/remote-api.js
// remote-api.js
const path = require('path');
const bodyParser = require('body-parser');
const app = require('express')();
const axios = require('axios');
const cors = require('cors');
require('dotenv').config({ path: path.resolve(process.cwd(), '.env.server') });
app.use(cors());
app.use(bodyParser.json());
app.get('/remote-api', (req, res) => {
req.body.sha256_key = "whatever you specify"
req.body.timestamp ="whatever you specify"
axios
.get(process.env.REMOTE_API_URL}, {
params: {
term: req.query.q
}
})
.then((response) => {
res.json({ data: response.data });
})
.catch((error) => {
res.json(error);
});
});
module.exports = app;
then you should specify this file in nuxt.config.js
serverMiddleware: ['~/api/remote-api'],
I am using expressJs and passport for authentication. I am using Google Oauth2.0 for login with standard Passport GoogleStrategy. At client I am using axios for sending a login request to the server. My login routes are :
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] }));
router.get(
"/google/callback",
passport.authenticate("google", { failureRedirect: "/"}),
function(req, res) {
const token = jwt.sign({id: req.user.id}, configAuth.secretKey);
console.log("generated token: ", token);
res.json({success: true, token: 'bearer ' + token});
}
);
I am using the user information from the callback to generate the JWT which I want to sent the client.
At the client I am using axios to send request and get the JWT and store it in localstore.
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})
.catch((error) => {
// if(error.response.status === 401) {
// this.setState({ message: 'Login failed. Username or password not match' });
// }
console.log("Login error", error);
});
But Axios doesn't wait for the redirect to happen and returns a HTML document with Loading... message. If you try to access the API in the browser, it returns the desired JSON object. Is there a way to wait for redirects. Should I use another library to send login request?
I tried sending the token as url parameter with
res.redirect()
but client and server are at different ports so it doesn't work.
Is there another way to do it?
Google's OAuth2 pathway redirects your browser, resulting in page reloads, a couple of times before it completes. As a result, your client-side code,
axios.get('http://localhost:3001/google')
.then((result) => {
console.log("result", result);
localStorage.setItem('jwtToken', result.data.token);
})...
will never reach the .then() block. You probably see this in the browser; you click a button or something to navigate to 'http://localhost:3001/google', and your localhost:3001 server re-directs your browser to a Google login page. Now that your browser is at the login page, it has no memory of the axios.get statement above--that webpage code is gone.
You need to handle the JWT in client-side code that your server sends in response to
axios.get('http://localhost:3001/google/callback').
This is your browser's final stop in the OAuth2 path--once you get there, you won't be re-directed again. You can put your axios.get function above inside that client-side code.
If you haven't solved the problem, there is a workaround use 'googleTokenStategy' instead of googleOAuth on passportjs. That way you can use react's GoogleLogin plugin to receive the access token from the front end and send it by axios.post to the backend link then set up the jwt. Reference here
I am trying to revoke oauth2 tokens using the stormpath API. Server-side authentication is performed using stormpath + express. Here is my request.
function revokeOauthTokens(params) {
// Revoke the oauth2 access. and refresh tokens
var oauthLogoutReq = {
method: 'POST',
url: params.apiBaseUrl + '/logout',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: 'grant_type=refresh_token&refresh_token='
+ params.oauth_tokens.refresh_token
}
return $http(oauthLogoutReq);
}
Where apiBaseUrl is my nodejs base url and ouath_tokens contains the response granted by a request to /oauth/token endpoint.
Looking at the documentation at the following links leaves me confused.
http://docs.stormpath.com/nodejs/express/latest/logout.html
http://docs.stormpath.com/guides/token-management/
Thanks.
This is a great question. As you’ve seen, express-stormpath is using secure, http-only cookies for token storage, and this doesn’t work for Cordova, Electron, etc, where cookie storage isn't implemented to spec. The alternative is local storage, or some other storage API that is provided to you (hopefully a secure one!).
The express-stormpath library does provide a /logout route, and it does revoke tokens, but it’s looking for the tokens in cookies. We need to add a new route, likely /oauth/revoke, to support explicit token revocation.
This is pretty easy to add right now as a custom route handler, and I’m including a link below. But please be aware that express-stormpath uses local token validation by default. This is done for speed (no roundtrip to our API) but the caveat is that your local server will NOT know that the tokens have been revoked, and can technically still be used for authentication if a malicious third party has stolen them from your client. If this is a concern you want to to address, you should opt-in to stormpath validation, which will always require a check against our token database. This is documented here:
http://docs.stormpath.com/nodejs/express/latest/authentication.html#token-validation-strategy
All that said, here is the route handler that you could wire up as /oauth/revoke, and have your Electron client use it to revoke the tokens when the user logs out:
'use strict';
var revokeToken = require('express-stormpath/lib/helpers/revoke-token');
function defaultResponder(res, err) {
if (err) {
console.error(err); // or your system logger
return res.status(err.status || 400).json({
message: err.developerMessage || err.message
});
}
res.end();
}
/**
* Implements the expected behavior of the /oauth/revoke endpoint, and requires
* that token_type be defined. This assumes that you are using the express-stormpath
* module, so that your Stormpath client and configuration context is available.
*
* #param {Object<ExpressRequest>} req Express JS Request
* #param {Object<ExpressResponse>} res Express JS Response
*/
function revokeTokens(req, res){
var client = req.app.get('stormpathClient');
var config = req.app.get('stormpathConfig');
var secret = config.client.apiKey.secret;
var token = req.body.token;
var token_type = req.body.token_type;
if (!token || ! token_type) {
defaultResponder(res, {
message: 'token and token_type fields are required'
});
}
if (token_type === 'access_token') {
revokeToken.revokeAccessToken(client, token, secret, defaultResponder.bind(null, res));
} else if (token_type === 'refresh_token') {
revokeToken.revokeRefreshToken(client, token, secret, defaultResponder.bind(null, res));
} else {
defaultResponder(res, {
message: 'invalid token_type'
});
}
}
module.exports = revokeTokens;
If you find that you don't want to use express-stormpath and would like to use something more direct, you can drop down to the Stormpath Node SDK and use it for token revocation:
https://docs.stormpath.com/nodejs/jsdoc/AccessToken.html
Or you can make DELETE requests directly against our API:
https://docs.stormpath.com/rest/product-guide/latest/auth_n.html#revoking-access-and-refresh-tokens
In both cases, you would be doing that from your server, not the Electron application.
I hope this helps!
-Robert
I am using 'googleapis' npm package to do token based google authentication.
I am redirected to '/api/auth/success/google' route inside express after google provided authentication and redirects us to the uri stated in google app credentials.
The problem I am facing is that ,I have retrieved the tokens on server side,but I am unable to send those tokens to client side for them to be saved in cookies.
The problem I am facing is because,'/api/auth/success/google' is redirected from google side and not an ajax call from client side.So if I send the tokens back in res,where will it redirect.Also please suggest a way to redirect from server side to client side,along with access_token.
server side code.
//Route reached after google successful login/authentication
app.get('/api/auth/success/google',function(req,res){
console.log("inside redirect");
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
oauth2Client.setCredentials(tokens);
}
res.sendFile('./index.html');
});
})
Client side call
//Google login requested from this function
googleLogin(){
event.preventDefault();
$.ajax({
type : 'POST',
url : baseURL + 'api/authenticate/google',
success: (function(data) {
if (data.redirect) {
document.location.href = data.redirect;
}
}).bind(this)
});
}
//Route handling request of google access
app.post('/api/authenticate/google',function(req,res){
// generate a url that asks permissions for Google+ and Google Calendar scopes
var scopes = [
googlecredentials.SCOPE[0],
googlecredentials.SCOPE[1]
];
var url = oauth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: scopes // If you only need one scope you can pass it as string
});
res.send({ redirect: url });
})
//Google App Credentials
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(googlecredentials.CLIENT_ID, googlecredentials.CLIENT_SECRET, googlecredentials.REDIRECT_URL);
googlecredentials.CLIENT_ID - 858093863410-j9ma1i7lgapupip1ckegc61plrlledtq.apps.googleusercontent.com
REDIRECT_URL - http://localhost:3000/api/auth/success/google where localhost:3000 runs server side
If you send the redirect URL back in the res, the client-side should be able to check for the presence of the redirect URL in the response, and if it exists, push your user to that URL.
This has probably been asked before, so a preemptive apology from me.
I built a site and I built an API. The API will also be used by a mobile app in the future. I own both so I'm pretty sure two and three legged OAuth aren't for me. The API has parts that are accessible to the world and other parts that are protected and require a user account. To keep things simple I've just gone with a https + Basic Auth solution (for now). It's all fine and good when testing requests manually to the API (I didn't write tests because I'm a bad person), things work as expected and Basic Auth is fine.
I'm trying to solve the flow of a user logging in with plaintext user and password, send that to the API to authenticate, the API just needs to say yes or no, yet all requests from the site (on behalf of a user) to the API should be signed in some way with their credentials for when they want to POST/GET/PUT/DEL one of the protected resources.
Out of all of the auth resources I've read I'm still confused as to what scheme to use. Storing the plaintext password on the site side so that I can base 64 encode it and send it over the wire seems bad, but it looks like that's what I'd have to do. I've read of digest auth but I'm not sure I get it. Any and all advice is welcome.
This is how I would handle this case;
POST the username and password as a plain text to your api using HTTPS of course.
Then validate it to your database, the best algorithm used nowadays to salt password is bcrypt.
If the user is not valid return 401, or whatever.
If the user is valid, return a JWT token with his profile signed with a Public Key algorithm.
Your fron-end knows the public key so it can decode the JWT but it can't generate a new one.
For every request that needs authentication, you attach an Authentication header, with Bearer [JWT]
A middleware in the backend reads this header and validate it with the private key.
Don't be affraid of JWT there are plenty of implementations for every language and framework and is easier than you might think. A lot of applications are already using JWT already even Google.
Auth0 is an authentication broker that can validate against any identity provider or custom database, and returns JWTs. It provides a clientID that can be used to decode the profile in the front end and a secret to validate the tokens in the backend as well as client side library to do this.
Disclaimer: I work for auth0.
Update: Since you mention node.js and express in comments I will give an example in this technology.
var http = require('http');
var express = require('express');
var jwt = require('jsonwebtoken'); //https://npmjs.org/package/node-jsonwebtoken
var expressJwt = require('express-jwt'); //https://npmjs.org/package/express-jwt
var secret = "this is the secret secret secret 12356";
var app = express();
app.configure(function () {
this.use(express.urlencoded());
this.use(express.json());
this.use('/api', expressJwt({secret: secret}));
});
//authentication endpoint
app.post('/authenticate', function (req, res) {
//validate req.body.username and req.body.password
//if is invalid, return 401
var profile = {
first_name: 'John',
last_name: 'Foo',
email: 'foo#bar.com',
id: 123
};
var token = jwt.sign(profile, secret, {
expiresInMinutes: 60*5
});
res.json({
token: token
});
});
//protected api
app.get('/api/something', function (req, res) {
console.log('user ' + req.user.email + ' is calling /something');
res.json({
name: 'foo'
});
});
//sample page
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
http.createServer(app).listen(8080, function () {
console.log('listening on http://localhost:8080');
});
This is an express application with one endpoint that validates username and password. If the credentials are valid it returns a JWT token with the full profile, with expiration 5 hours.
Then we have an example endpoint in /api/something but since I've a express-jwt middleware for everything on /api it requires a Authorization: Bearer header with a valid token. The middleware not only validates the token but also parses the profile and put it on req.user.
How to use this client-side? This is an example with jquery:
//this is used to parse the profile
function url_base64_decode(str) {
var output = str.replace("-", "+").replace("_", "/");
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
throw "Illegal base64url string!";
}
return window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
}
var token;
//authenticate at some point in your page
$(function () {
$.ajax({
url: '/authenticate',
type: 'POST',
data: {
username: 'john',
password: 'foo'
}
}).done(function (authResult) {
token = authResult.token;
var encoded = token.split('.')[1];
var profile = JSON.parse(url_base64_decode(encoded));
alert('Hello ' + profile.first_name + ' ' + profile.last_name);
});
});
//send the authorization header with token on every call to the api
$.ajaxSetup({
beforeSend: function(xhr) {
if (!token) return;
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}
});
//api call
setTimeout(function () {
$.ajax({
url: '/api/something',
}).done(function (res) {
console.log(rest);
});
}, 5000);
First, I've an authenticate call with the username and password, I can decode the profile in the JWT to get the user profile and I also save the token to use in every request later on.
The ajaxSetup/beforeSend trick adds the header for every call. So, then I can make a request to /api/something.
As you can imagine this approach doesn't use cookies and sessions so it works out of the box in CORS scenarios.
I'm a big fan of passport.js and I've contributed a lot of adapters and fixes for some other adapter but for this particular case I wouldn't use it.
I've been thinking about a similar scenario lately; here's what I did:
SSL + Basic Auth
In the DB (on the API side), generate a random salt (per user), and save the salt and the hashed (password + salt). When a request arrives, throw on the salt and hash it, then compare to what you've saved
Send the password in plaintext - you are using SSL so I think this is okay (this is the part I am most uncertain of)
I don't have a great reason for recommending this but in case you have a reason to use it:
.4. Attach a timestamp to every request and have them expire after a couple of minutes.
The reason you should save salted-and-hashed passwords in your DB is in case someone steals your DB.
Basically I'm putting a lot of faith into SSL, and what I've read tells me that's okay.