I am implementing OAuth Google Sign in using backend (written in node.js, express framework at Heroku). Front end is Android and it sends the token id to the server just fine. And server receives the token id correctly.
Here is the code (which is ripped off straight from Google Documents)
var auth = new GoogleAuth;
var client = new auth.OAuth2(CLIENT_ID, '', '');
client.verifyIdToken(
token,
CLIENT_ID,
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3],
function(e, login) {
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
But at times login in undefined. Its so strange that this problem occurs 1/10 rather than for every try so that I am not able to track the source of issue. For every other 9/10 it works just fine.
Any suggestions on how to solve this?
The problem in your code is that you are not checking if your callback get's any error.
The standard way in node.js to use a callback function is using two parameters - error is the first, the actual (success) returned data is the second, and the convention is that if an error exists - you should address it, and you're not gauranteed to get the data, and if everything went well- error will be null and you'll get your data.
So in your code, you are not checking that there's an error (and like you say, not always there's one).
Should be something like:
function(e, login) {
if (e) {
// handle error here
return; // don't continue, you don't have login
}
// if we got here, login is defined
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
The first parameter to the callback function is an error that needs to handled.
function(error, login) {
if (error) return console.error(error)
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
Related
I have a website running on www.mywebsite.com. The files are hosted in an S3 bucket in combination with cloudFront. Recently, I have added a new part to the site, which is supposed to be only for private access, so I wanted to put some form of protection on there. The rest of the site, however, should remain public. My goal is for the site to be accessible for everyone, but as soon as someone gets to the new part, they should not see any source files, and be prompted for a username/password combination.
The URL of the new part would be for example www.mywebsite.com/private/index.html ,...
I found that an AWS Lambda function (with node.js) is good for this, and it kind of works. I have managed to authenticate everything in the entire website, but I can't figure out how to get it to work on only the pages that contain for example '/private/*' in the full URL name. The lambda function I wrote looks like this:
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
// Configure authentication
const authUser = 'USER';
const authPass = 'PASS';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
The part that doesn't work is the following part:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
If you're using a Lambda#Edge function (appears you are). Then you can view the Request Event structure here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
The problem might be this line:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
If you're strictly looking to check if a JavaScript string contains another string in it, you probably want to do this instead:
if (!request.uri.toLowerCase().indexOf("/private/") !== -1) {
Or better yet, using more modern JS:
if (!request.uri.toLowerCase().includes("/private/")) {
I have a project I build with React and Strapi headless CMS (using nodejs). The backend, which is handled using Strapi, resides in port 443. When I send a GET request to any url in backend using http://site:443 I get a valid response, but a POST request always returns an error 404.
I've used an external tool https://reqbin.com/ to test it and got the same results.
The interesting thing is that even though I get 404 response, the route is being handled and executed, and yet gives a 404 response back.
Why could this be happening?
Thanks in advance
Here is the code upon reaching this route:
/**
* Create a/an orders record.
*
* #return {Object}
*/
create: async (ctx) => {
let params = ctx.request.body;
if (params.Sum) { // only if we got a good POST response
// check secret code
let a = params.UniqueID;
var res = a.substr(a.length - 12, a.length);
if (res !== 'secret') {
ctx.throw(500, 'SECURITY BLOCK', { expose: true });
return null;
}
var username = a.substr(0, a.length - 12);
// create validity
var date = new Date();
if (params.Sum === '250') { // one month membership
date.setTime( date.getTime() + 31 * 86400000 );
} else if (params.Sum === '390') { // one month membership
date.setTime( date.getTime() + 180 * 86400000 );
}
// create order in database
let today = new Date();
// extract username
var n = params.CustomerName.indexOf("_");
var name = params.CustomerName.substr(0, n);
var instId = params.CustomerName.substr(n+1, params.CustomerName.length);
const order = await strapi.services.orders.add({
userId: username,
username: name,
institutionId: instId,
sum: params.Sum,
transactionDate: today,
validity: date,
cardNum: params.CardNum,
cardName:params.CardName,
});
let axiosArr = {
institutions: [{_id: instId}],
validity: date,
secret: 'secret'
}
axios.put(apiUrl+'/users/'+username, axiosArr).then(() => {return order})
// return order
Most of the code is irrelevant to the question, I was thinking maybe the reason that the response is 404 is because I'm not returning the order object right away, but only after the axios.put?
Unfortunately I cannot test it right now
If the route is well executed but it returns a 404, it's because the response body is empty.
In your code your commented the return order.
Add in your code something like ctx.send({order}) or un comment your return
You will no longer have a 404.
I'm not familiar with strapi but it doesn't sound like an auth issue to me. Are you sure these routes are set up to accept POST requests and not just GET requests? It's common to return 404s if a user is missing a permission needed to access a resource, to not let the person know that resource exists but without any sample code I can't really say for sure what's happening in your case. Could you link more code?
I am new to nodejs and working on a proof of concept just for fun.
Background:
I have a cloud directory of user information (like username, password and other info). This cloud directory can be used to authenticate a user only via restful API (i.e. no direct connectivity using LDAP or JDBC etc.).
Aim:
To build an LDAP interface for this cloud directory. To start with I am interested only in authentication (LDAP bind).
Intended Flow:
LDAPClient initiates a standard LDAP simple BIND request:
Host: host where my nodejs app will run
Port: 1389 (port that my nodejs app will be bound to)
Username: a user from cloud directory
Password: user's password
This request is received by my NodeJS app (I am using ldapjs module).
// process ldap bind operation
myLdapServer.bind(searchBase, function (bindReq, bindRes, next) {
// bind creds
var userDn = req.dn.toString();
var userPw = req.credentials;
console.log('bind DN: ' + req.dn.toString());
...
...
}
Within the above callback, I must use http.request to fire a restful API (POST) to the cloud directory with the details I received from the BIND request (i.e. username, password).
If restful api response status is 200 (auth success), then I must return success to the LDAPClient, else I must return invalid credentials error.
Success:
bindRes.end();
return next();
Failure:
Console.log("returning error");
return next(new ldap.InvalidCredentialsError());
Questions:
Is this possible using NodeJS? Asking because of the nesting involved as evident above (calling of REST API from within a callback). Also since this is an authentication operation, this is meant to be a blocking operation(?)
Thanks,
Jatin
UPDATE:
Thanks Klvs, my solution is more or less like the one you posted. Please have a look at the snippet below:
// do the POST call from within callback
var postRequest = https.request(postOptions, function(postResponse) {
console.log("statusCode: ", postResponse.statusCode);
if(postResponse.statusCode!=200) {
console.log("cloud authentication failed: "+postResponse.statusCode);
return next(ldapModule.InvalidCredentialsError());
} else {
postResponse.on('data', function(d) {
console.info('POST result:\n');
process.stdout.write(d);
console.info('\n\nPOST completed');
});
res.end();
return next();
}
});
// write json data
postRequest.write(postData);
postRequest.end();
postRequest.on('error', function(e) {
console.error("postRequest error occured: "+e);
});
Successful authentication works fine, however, failed authentication does not send any response back to the LDAPClient at all. My client just times out instead of showing authentication failure error. I do see the "cloud authentication failed: " log message on the Node console, which means the below statement is not doing what I intend it do:
return next(ldapModule.InvalidCredentialsError());
Note that the above statement works when I remove the rest call etc, and just return the error back to the client.
Am I missing something?
Thanks,
Jatin
Of course it's possible in nodejs. If I understand you want to make an authenticating request to a server and have it either fail or succeed.
const request = require('request')
// process ldap bind operation
myLdapServer.bind(searchBase, function (bindReq, bindRes, next) {
// bind creds
var userDn = req.dn.toString();
var userPw = req.credentials;
console.log('bind DN: ' + req.dn.toString());
request.post({username: userDn, password: userPw}, (err, res, body)=>{
if(err) {
console.log("returning error");
next(new ldap.InvalidCredentialsError());
} else {
bindRes.end();
next();
}
})
}
Is that what you're looking for? If so, you just need to get accustom to callbacks.
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
How to check if youtube video exists on node.js app server side:
var youtubeId = "adase268_";
// pseudo code
youtubeVideoExist = function (youtubeId){
return true; // if youtube video exists
}
You don't need to use the youtube API per-se, you can look for the thumbnail image:
Valid video = 200 - OK:
http://img.youtube.com/vi/gC4j-V585Ug/0.jpg
Invalid video = 404 - Not found:
http://img.youtube.com/vi/gC4j-V58xxx/0.jpg
I thought I could make this work from the browser since you can load images from a third-party site without security problems. But testing it, it's failing to report the 404 as an error, probably because the content body is still a valid image. Since you're using node, you should be able to look at the HTTP response code directly.
I can't think of an approach that doesn't involve making a separate HTTP request to the video link to see if it exists or not unless you know beforehand of a set of video IDs that are inactive,dead, or wrong.
Here's an example of something that might work for you. I can't readily tell if you're using this as a standalone script or as part of a web server. The example below assumes the latter, assuming you call a web server on /video?123videoId and have it respond or do something depending on whether or not the video with that ID exists. It uses Node's request library, which you can install with npm install request:
var request = require('request');
// Your route here. Example on what route may look like if called on /video?id=123videoId
app.get('/video', function(req, response, callback){
var videoId = 'adase268_'; // Could change to something like request.params['id']
request.get('https://www.youtube.com/watch?v='+videoId, function(error, response, body){
if(response.statusCode === 404){
// Video doesn't exist. Do what you need to do here.
}
else{
// Video exists.
// Can handle other HTTP response codes here if you like.
}
});
});
// You could refactor the above to take out the 'request.get()', wrap it in a function
// that takes a callback and re-use in multiple routes, depending on your problem.
#rodrigomartell is on the right track, in that your check function will need to make an HTTP call; however, just checking the youtube.com URL won't work in most cases. You'll get back a 404 if the videoID is a malformed ID (i.e. less than 11 characters or using characters not valid in their scheme), but if it's a properly formed videoID that just happens to not correspond to a video, you'll still get back a 200. It would be better to use an API request, like this (note that it might be easier to use the request-json library instead of just the request library):
request = require('request-json');
var client = request.newClient('https://www.googleapis.com/youtube/v3/');
youtubeVideoExist = function (youtubeId){
var apikey ='YOUR_API_KEY'; // register for a javascript API key at the Google Developer's Console ... https://console.developers.google.com/
client.get('videos/?part=id&id='+youtubeId+'&key='+apikey, function(err, res, body) {
if (body.items.length) {
return true; // if youtube video exists
}
else {
return false;
}
});
};
Using youtube-feeds module. Works fast (~200ms) and no need API_KEY
youtube = require("youtube-feeds");
existsFunc = function(youtubeId, callback) {
youtube.video(youtubeId, function(err, result) {
var exists;
exists = result.id === youtubeId;
console.log("youtubeId");
console.log(youtubeId);
console.log("exists");
console.log(exists);
callback (exists);
});
};
var notExistentYoutubeId = "y0srjasdkfjcKC4eY"
existsFunc (notExistentYoutubeId, console.log)
var existentYoutubeId = "y0srjcKC4eY"
existsFunc (existentYoutubeId, console.log)
output:
❯ node /pathToFileWithCodeAbove/FileWithCodeAbove.js
youtubeId
y0srjcKC4eY
exists
true
true
youtubeId
y0srjasdkfjcKC4eY
exists
false
false
All you need is to look for the thumbnail image. In NodeJS it would be something like
var http = require('http');
function isValidYoutubeID(youtubeID) {
var options = {
method: 'HEAD',
host: 'img.youtube.com',
path: '/vi/' + youtubeID + '/0.jpg'
};
var req = http.request(options, function(res) {
if (res.statusCode == 200){
console.log("Valid Youtube ID");
} else {
console.log("Invalid Youtube ID");
}
});
req.end();
}
API_KEY is not needed. It is quite fast because there is only header check for statusCode 200/404 and image is not loaded.