Send a SAML request by POST using saml2-js in Node - node.js

I've gone through the documentation (however limited) to connect to an IDP. It's all configured and working properly except one thing. The IDP won't accept SAML Requests via GET.
Does saml2-js support HTTP POST for sending SAML requests to the IDP and if so, how is this coded? If not, is there an alternative NPM package that would work?
Currently i have:
sso.sp.create_login_request_url(sso.idp,{},(err, login_url, requestId) => {
console.log('err',err)
console.log('login_url',login_url)
console.log('requestId',requestId);
response.redirect(login_url);
});

An addition to jsub's answer:
The POST request to the IdP must be made by the browser, not by the server (by needle in jsub's case). Only the browser contains the IdP session cookie which authenticates the user with the IdP. res must contain an HTML page with an auto-submitting <form> with one <input> per param:
app.get("/login", function(req, res) {
sso.sp.create_login_request_url(sso.idp, {}, function(err, login_url, requestId) {
var url = new URL(login_url);
res.type("html");
res.write(`<!DOCTYPE html><html>
<body onload="document.querySelector('form').submit()">
<form action="${url.protocol}//${url.host}${url.pathname}" method="post">`);
for (const [param, value] of url.searchParams)
res.write(`<input name="${param}" value="${value}"/>`);
res.end(`</form></body></html>`);
});
});

I am trying to work around this as well, and my attempts have not worked but the approach is to make a separate http POST request with a client (using needle in my case) and then try to pipe the response from that into the response for the handler, e.g. something like this:
sso.sp.create_login_request_url(sso.idp, {}, (err, login_url, requestId) => {
// response.redirect(login_url);
const [url, param] = login_url.split("?")
const postOptions = {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}
needle.post(Url, param, postOptions, (err, postResponse) => {
postResponse.pipe(res)
});
However I am not having much luck, trying to dig into why the pipe does not work
EDIT: the piping seems to work when I do it in this short form
needle.post(url, param, postOptions).pipe(res)

Related

Node.js Axios HTTP request with bearer token returning undefined

I've seen many posts on Stack that are close to what I need, but don't fully answer my question (I'm pretty green with Node). I'm working on connecting a Twitch/Tiltify donation campaign to a Raspberry Pi via Node.js/Axios. I'd like the Pi to regularly check for new donations, then activate physical circuits (solenoid valves etc.) to be viewed live on the stream. Here's my code so far:
const axios = require('axios');
axios.get('URL_GOES_HERE', {
headers: {
'Authorization' : 'Bearer MY_TILTIFY_ACCESS_TOKEN'
}
})
.then(response => {
console.log(response.data.url);
console.log(response.data.explanation);
})
.catch(error => {
console.log(error);
});
I assume that MY_TILTIFY_ACCESS_TOKEN is the access token I generated from within my Tiltify account. I'm confused, however, about what value to put in URL_GOES_HERE. The somewhat sparse Tiltify API docs give two possible URLS: https://tiltify.com/oauth/authorize and https://tiltify.com/oauth/token. Or am I supposed to put my bearer credentials directly into the URL of a useful request, like https://tiltify.com/api/v3/user? I've tried all three, and I just get undefined undefined in the console.
A nudge in the right direction is appreciated! Thanks for your time.
#benstepp over on Github ultimately answered my question. Here's the code he provided:
const axios = require('axios');
axios.get('https://tiltify.com/api/v3/campaigns/MY_CAMPAIGN_ID/rewards', {
headers: {
'Authorization' : 'Bearer MY_API_TOKEN'
}
})
.then(response => { // this is an axios response object (https://github.com/axios/axios#response-schema)
//console.log(response.data); // this is the response body from tiltify (https://tiltify.github.io/api/endpoints/campaigns-id-donations.html)
//console.log(response.data.data); // this is the .data property of our responses
response.data.data.map((reward) => {
// the name/amount of the recent donations
console.log(`${reward.name}`)
})
})
.catch(error => {
console.log(error);
});
The /authorize endpoint is used for the Web Server OAuth Authentication Flow and User-Agent OAuth Authentication Flow.
The /token endpoint is used for the Username-Password OAuth Authentication Flow and the OAuth Refresh Token Process.
So first you need to get Authorized to be able to use Tiltify api. For that you need to use either of the flow
https://tiltify.com/oauth/authorize
https://tiltify.com/oauth/token
Assuming you used token route, you will get a response something like this:
{ "access_token":"token", "token_type":"bearer", "refresh_token":"refresh_token" }
Then using the access_token you got from the response you will call the api routes so in URL GOES HERE will be your api routes like
/campaigns/:id
causes/:id
with which you'll use Authorization: Bearer <access_token> in headers

how can I get an element in a redirect url using node.js

Non-English country, please forgive my spelling mistakes.
For example, I want to first redirect url1(http://localhost:3000/api/song/167278) to url2(http://localhost:4000/api/song/167278) to use url2's api. And url2 will reponse a json file, which can be seen in the postman's panel.
(postman's pannel)
But there maybe a lot of elements, I only want an element in the file, such as data[0].url. How can I return just return the url value (data[0].url in this json) when people access http://localhost:3000/api/song/167278.
I am using express.js now, how can I edit it? Or is there any other methods?
app.get('api/song/:id', async (req, res) => {
try {
const { id } = req.params
url = "http://localhost:4000/api/song/" + id
res.redirect(url)
}
catch (e) {
console.log(e)
}
}
You could either proxy the entire request there or fetch localhost:4000/api/song/1 in your request handler (with something like node-fetch or axios or with node's APIs and send the fields that you want back to the client as json.

How to include access-token in the HTTP header when requesting a new page from browser

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

nodejs request.post (user, pass) and get data from another url after auth

I want to get some data that are available after authentication. I pass through a post login and password on the page http://site.domain.com/auth.html and I want to get html from another page http://site.domain.com/anotherpage.html
request.post({followAllRedirects: true, url:'http://site.domain.com/auth.html', form:{user:'login#domain.com', pass:'password'}},
function (error, response, body) {
if (!error) {
request('http://site.domain.com/anotherpage.html', function(error, response, html){
fs.appendFileSync('log.txt', html, encoding='utf8');
});
}
});
Authentication takes place normally (there is a message in the html with greeting), after request I get the data as if the authentication is not passed.
fixed result:
var j = request.jar(); var request = request.defaults({jar:j});
and then my code
Most often than not, in the web, authentication information is store as cookies in the user's browser. Because this is a server request, I don't think two "unrelated" requests is going to cut it, as no header information pertaining to the first request is being sent along with the second request. Perhaps you could try this strategy or some other similar procedure to mimic that header interaction.
I found a solution, I put in top of my code, these lines
var j = request.jar(); var request = request.defaults({jar:j});
jar - If true, remember cookies for future use (or define your custom cookie jar;

How can you use cookies with superagent?

I'm doing cookie session management with express with something like this:
req.session.authentication = auth;
And I verify the authenticated urls with something like
if(!req.session.authentication){res.send(401);}
Now I'm building tests for the URLs with mocha, superagent and should, however I can't seem to find a way to get/set the cookie with superagent. I even tried to request the login before the authenticated test but it is not working,
I have tried adding the request to the login in the before statement for the mocha BDD suite, however it is still telling me that the request is unauthorized, I have tested the authentication doing the requests from the browser, however it is not working from the suite any ideas why?
Use superagent.agent() (instead of plain old superagent) to make requests have persistent cookies. See 'Saving cookies' in the superagent docs, or the code examples: agency.js, controller.test.js.
Seems like following code works fine;
req.set('Cookie', "cookieName1=cookieValue1;cookieName2=cookieValue2");
If the issue is in sending cookies for CORS requests use .withCredentials() method
described here
request
.get('http://localhost:4001/')
.withCredentials()
.end(function(err, res) { })
Since you mentioned you need to both get and set the cookie:
Get:
const request = await Superagent.get('...')
const cookie = request.header['set-cookie']
Set:
Superagent.post('...').set('Cookie', 'cookie_info')
2020 +
A clean way to do it is:
create a simple cookie store
abstract set Cookie to send it in each request
update the cookie only when needed
Note I keep the same URL because I use graphql but you can make it a parameter:
const graph = agent =>
agent.post('/graph')
.set('cookie', cookieStore.get());
const handleCookie = res =>
cookieStore.set(res.headers['set-cookie'][0]);
let currentCookie="";
const cookieStore = {
set: cookie=>{currentCookie=cookie},
get: cookie=>currentCookie,
};
module.exports = {graph,connectTestUser,handleCookieResponse};
You can now just use graph(agent) to send a request and handleCookie(response) when you have a response that may update your cookie (set or clear), example:
graph(agent).end((err,res) => {
if (err) return done(err);
res.statusCode.should.equal(200);
handleCookie(res);
return done();
});
Add a cookie to agent cookiejar:
const request = require('superagent');
const {Cookie} = require('cookiejar')
const agent = request.agent()
agent.jar.setCookie(new Cookie("foo=bar"))

Resources