We have two separate websites / apps in same domain but different subdomains.
E.g.
https://hello.website.com (Webapp 1)
https://world.website.com (Webapp 2)
What we’d like to do is to login users at Webapp 1 and upon logging in and clicking a button within Webapp 1, we’d like to redirect the user to Webapp 2. However, Webapp 2 needs the same authentication token which is currently stored in the localstorage of Webapp 1. How do I make the localstorage content available to Webapp 2?
Or is there a better way to do this?
Since the domains are not the same, transferring information from the local storage of one of them to another isn't possible directly, but since the sites are on HTTPS, it should be safe and easy enough to send the authentication token as search parameters. For example, when redirecting, instead of redirecting to https://world.website.com, instead take the current authentication token for https://hello.website.com and append it, then redirect:
const url = 'https://world.website.com?token=' + authToken;
window.location.href = url;
(if the authentication token may have special characters in it, you may need to escape them first)
Then, on the other domain, check to see if there's a token in the search parameters, and if so, extract it and save it to localStorage:
const paramsMatch = window.location.href.match(/\?.+/);
if (paramsMatch) {
const params = new URLSearchParams(paramsMatch[0]);
const authToken = params.get('token');
if (authToken) {
localStorage.authToken = authToken;
}
}
Because the domains are on HTTPS, putting the token in the URL is mostly safe - eavesdroppers will not be able to see it. But if your server that handles the requests saves logs, you may find it undesirable for the server to have its logs include authentication tokens as a result of this approach.
Another way would be for:
Webapp 1 to make a POST request to Webapp 2 with the token in the payload (where Webapp 2 has the appropriate CORS settings)
Webapp 2 generates a new unique URL (that expires after, say, 30 minutes), associates the token with that URL, and responds to the client on Webapp 1 with the URL
The client receives the response from Webapp 2 and then navigates to the unique URL on Webapp 2 that it was just given
Webapp 2, when handling the request, sees that the unique URL was associated with a token, and goes through the process of fully associating that token with the request session
That's the limitation of localstorage and sessionstorage. You can't. There are some workarounds with iframe, but those are neither elegant nor secured. You should use cookie with appropriate domain attribute domain=example.com. You may also want to read the following answer for security with cookie vs localstorage: https://stackoverflow.com/a/54258744/1235935
Related
I am working on a php website. I have a page called pageX.php, and on that page I make an Ajax call:
xmlhttp.open("GET", "/getData/dat?q=" + str, true);
I want to avoid displaying data that is contained in this request. Also I want to be able to respond to this request only if it comes from pageX.php, and nowhere else.
I have tried using PHP sessions but it didn't work – the server still responds to the request if you go directly from the browser address bar to
mysite.com/getData/dat?q=1
Can you help with a solution for this problem?
You said you tried using sessions. I'd say that would be a quick and easy way to do it without going down the path of various Authentication protocols like OAuth.
Using sessions would mean that if you use the same browser as your current pageX.php, you will be able to access mysite.com/getData/dat?q=1 from another tab.
This is because that browser would still be sending session cookies to your host. However, if you try using a new browser or a new device, you should not be able to make that request as you no longer have the same session cookies.
So your only risk is that someone else with access to your user's computer might be able to access PageX.php and their session.
So this is a good compromise, if you think that whoever is using PageX.php is not using it on a shared computer.
CORS is not required to send requests to resources on the same domain since it is sent from the same origin. The issue here is that you have an open route which takes in arguments that respond with sensitive information based on that primary key. You need to close that route.
In order to do so, you can use something like JWT to generate bearers for users. Sort of like, "Remember me tokens". This can be generated when the user authenticates and be stored as a session like so:
session_start();
/**
* Your authentication logic here
* Which for example returns a uid of 1
*/
$_SESSION['ssid'] = \Firebase\JWT\JWT::encode((object) ['uid' => 1], 'your_key');
Your closed route would now look something like this:
session_start();
header('Content-Type: application/json');
if(!isset(($bearer =& $_SESSION['ssid'])))
die(json_encode(['error' => 403])); // Forbidden
try {
$user = \Firebase\JWT\JWT::decode($bearer, 'your_key', array('HS256'));
// Your logic
// $user->uid to get the ID
} catch (Exception $e) {
// Handle exceptions where the bearer has either expired or is invalid
}
Then, (JQuery example) you'd be able to simply request the closed route like so:
(function ($) => {
$.get('/getData', (response) => {
// Logic
});
})(JQuery);
This now stops users being able to forge the request with their own arguments.
I have an app that has a REST api. I want it so that the only requests that can be made to the REST api are ones originating from the app itself. How can I do that?
I am using a node.js+express server too.
EDIT: the app is fully a public web app.
Simply define the header in your request, what this does is, it allows requests only from a certain domain, and instantly rejects any other domain.
response.set('Access-Control-Allow-Origin', 'domain.tld');
EDIT: IF you're really keen against web scraping stuff, you could make a function to double check client's origin.
function checkOrigin (origin) {
if (origin === "your.domain.tld") {
return true;
} else {
return false;
}
}
/* Handling it in response */
if (checkOrigin(response.headers.origin)) {
// Let client get the thing from API
} else {
response.write("Send them error that they're not allowed to use the API");
response.end();
}
Above example should work for the default HTTP/HTTPS module, and should also work for Express, if I'm not mistaken.
EDIT 2: To back my claim up that it should also work for Express, I found this quotation at their documentation;
The req (request) and res (response) are the exact same objects that Node provides, so you can invoke req.pipe(), req.on('data', callback), and anything else you would do without Express involved.
I would recommend using an API key from the client. CORS filters are too easy to circumvent.
A simple approach for securing a How to implement a secure REST API with node.js
Overview from above post:
Because users can CREATE resources (aka POST/PUT actions) you need to secure your api. You can use oauth or you can build your own solution but keep in mind that all the solutions can be broken if the password it's really easy to discover. The basic idea is to authenticate users using the username, password and a token, aka the apitoken. This apitoken can be generated using node-uuid and the password can be hashed using pbkdf2
Then, you need to save the session somewhere. If you save it in memory in a plain object, if you kill the server and reboot it again the session will be destroyed. Also, this is not scalable. If you use haproxy to load balance between machines or if you simply use workers, this session state will be stored in a single process so if the same user is redirected to another process/machine it will need to authenticate again. Therefore you need to store the session in a common place. This is typically done using redis.
When the user is authenticated (username+password+apitoken) generate another token for the session, aka accesstoken. Again, with node-uuid. Send to the user the accesstoken and the userid. The userid (key) and the accesstoken (value) are stored in redis with and expire time, e.g. 1h.
Now, every time the user does any operation using the rest api it will need to send the userid and the accesstoken.
I have a node.js REST API and I want to restrict POST/PUT/DELETE calls to a predefined list of "sources" (web applications which I do not own the code).
The only way I see to achieve this is to put a token on the client-side (something like Google Analytics in JS files) but I have no idea how to secure this since the token will be accessible in the static files.
What strategy should I use ? JWT and OAuth2 seem not indicated since it requires first user authentication, but what I want to authenticate is not user but webapps.
Your question is slightly unclear. You could mean either (a) that you want to strongly encourage the user to use the app and prevent other code from maliciously making your user perform an action, or (b) that you want to absolutely prevent your user from using other code to access your server.
The first option is possible, and indeed a very good idea. The second is impossible, based on the way the Internet works.
First, the impossibility. Essentially, client-side code is there to make life easier for your client. The real work will always be done on the server side -- even if this only means validating data and storing it in the database. Your client will always be able to see all the HTTP requests that they send: that's the way HTTP works. You can't hide the information from them. Even if you generate tokens dynamically (see below), you can't prevent them from using them elsewhere. They can always build a custom HTTP request, which means ultimately that they can, if they really, really want, abandon your app altogether. Think of your client-side code as merely making it easier for them to perform HTTP requests and abandon any idea of preventing them "doing it wrong"!
The much better option is CSRF protection, which gives the best possible protection to both your server and the client. This means sending a generated token to your client when they first log on and verifying it (either by looking it up or decrypting it) when they send it on every request. This is the basis of JWT, which is a beautiful implementation of a fairly old system of verification.
In the end your API is public, since any random website visitor will have to be able to interact with the API. Even if you use tokens to restrict access somewhat, those tokens by definition will have to be public as well. Even regularly expiring and renewing the tokens (e.g. through a backend API, or by including a nonce algorithm) won't help, since those new tokens will again be publicly visible on the 3rd party's website where anyone can fetch one.
CSRF protection can help a little to avoid cross-site abuse within browsers, but is ultimately pointless for the purpose of preventing someone to write an API scraper or such.
The best you can do is use the tokens to identify individual site owners you granted access to, vigilantly monitor your API use, invalidate tokens when you think you're seeing them abused and contact the site owners about securing their tokens better somehow (which they'll have the same problem doing, but at least you have someone to blame cough cough).
You can use hmac to secure this :
Each client has a unique couple of key public/private (for example "public" and "private").
When client send request, he has to send a nonce + his user public key + the hmac of nonce+public key with his private key.
When server handle request, the server retrieve the client according to his public key, get the secret key of the user, then verify the signature.
Client, sample call on /api
var nonce = "randomstring";
var pk = "aaa";
var sk = "bbb";
var string = "pk="+pk+"&nonce="+nonce;
var crypto = require('crypto');
var hmac = crypto.createHmac('sha512', sk).update(string).digest('hex');
// send this payload in your request in get, put, post, ....
var payload = string+"&hmac="+hmac;
request.post({uri:"website.com/api?"+payload}, ....
And
Server side, security check
var nonce = req.query.nonce;
var pk = req.query.pk;
var hmac = req.query.hmac;
// retrieve user and his sk according to pk
var sk = getUser(pk).sk
// rebuild payload string
var string = "pk="+pk+"&nonce="+nonce;
var crypto = require('crypto');
var hmac_check = crypto.createHmac('sha512', sk).update(string).digest('hex');
if(hmac_check === hmac) { // request valid }else{ // invalid request }
I am using Thinktecture AuthorizationServer (AS) and it is working great.
I would like to write a native javascript single page app which can call a WebAPI directly, however implicit flow does not provide a refresh token.
If an AJAX call is made, if the token has expired the API will send a redirect to the login page, since the data is using dynamic popups it will this will interrupt the user.
How does Facebook or Stackoverflow do this and still allow the javascript running on the page to call the APIs?
Proposed Solution
Does the below scenario sound sensible (assuming this can be done with iframes):
My SPA directs me to the AS and I obtain a token by Implicit Flow. Within AS I click allow Read data scope, and click Remember decision, then Allow button.
Since I have clicked Remember decision button, whenever I hit AS for a token, a new token is passed back automatically without me needing to sign in ( I can see FedAuth cookie which is remembering my decision and believe this is enabling this to just work).
With my SPA (untrusted app), I don't have a refresh-token only an access token. So instead I:
Ensure user has logged in and clicked remember decision (otherwise iframe wont work)
Call WebAPI, if 401 response try and get a new token by the below steps...
Have a hidden iframe on the page, which I will set the URL to get a new access-token from the Authorisation Server.
Get the new token from the iframe's hash-fragment, then store this in the SPA and use for all future WebAPI requests.
I guess I would still be in trouble if the FedAuth cookie is stolen.
Any standard or recommended way for the above scenario?
I understand that your problem is that the user will experience an interruption when the access token has expired, by a redirection to the login page of the authorization server. But I don't think you can and should get around this, at least, when using the implicit grant.
As I'm sure you already know, the implicit grant should be used by consumers that can NOT keep their credentials secret. Because of this, the access token that is issued by an authorization server should have a limited ttl. For instance google invalidates their access token in 3600 sec. Of course you can increase the ttl, but it should never become a long lived token.
Also something to note is that in my opinion the user interruption is very minimal, i.e if implemented correctly, the user will only have to authenticate once with the authorization server. After doing that (for example the first time when also authorizing the application access to whatever resources the user controls) a session will be established (either cookie- or token based) and when the access token of the consumer (web app using implicit grant) expires, the user will be notified that the token has expired and re authentication with the authorization server is required. But because a session already has been established, the user will be immediately redirected back to the web app.
If however this is not what you want, you should, in my opinion, consider using the authorization code grant, instead of doing complicated stuff with iframes.
In that case you need a server side web application because then you can keep your credentials secret and use refresh tokens.
Sounds like you need to queue requests in the event that an access token expires. This is more or less how Facebook and Google do it. A simple way using Angular would be to add a HTTP Interceptor and check for HTTP401 responses. If one is returned, you re-authenticate and queue any requests that come in after until the authentication request has completed (i.e. a promise). Once that's done, you can then process the outstanding queue with the newly returned access token from your authentication request using your refresh token.
Happy Coding.
Not sure if I understand your question but,
I would like to write a native javascript single page app which can call a WebAPI directly, however implicit flow does not provide a refresh token.
Summarize facts,
refresh token is sometimes used to be a part of A: Authorization Grant
https://www.rfc-editor.org/rfc/rfc6749#section-1.5
and as you said in implicit flow you dont get back refresh token, but only in Authorization Grant part
https://www.rfc-editor.org/rfc/rfc6749#section-4.2.2
so you can get back refresh token when issuing access token (refresh tokens are always optional)
https://www.rfc-editor.org/rfc/rfc6749#section-5.1
With my SPA (untrusted app), I don't have a refresh-token only an
access token. So instead I:
Ensure user has logged in and clicked remember decision (otherwise
iframe wont work)
Call WebAPI, if 401 response try and get a new
token by the below steps...
Have a hidden iframe on the page, which
I will set the URL to get a new access-token from the Authorisation
Server.
Get the new token from the iframe's hash-fragment, then
store this in the SPA and use for all future WebAPI requests.
SPA(you) have no idea if user selected remember decision. Its in AS direction and should be complete blackbox. Skip this step.
You can try to use access token and wait for result, always.
If access token has expired and you dont have refresh token, you still can create hidden iframe and and try to get new access token.
Lets assume your AS provide option to remember decision and wont change it in future, then: your iframe will get new access token without user interaction, then you will get result back in some unknown time limit.
Result can be checked by setInterval for read specific cookie or iframe postmessage.
If you dont get back data in time limit, then one from following scenarios occured:
lag, AS is slow, connection is slow or time limit is too tight
user didnt select remember decision
In this case:
show iframe with login
I consider scenario above as good practise if AS doesnt provide refresh tokens, but I also guess every AS like that wont provide remember option as well.
StackOverflow <---> Google scenario (I can only guess)
User login, authorization request occured
User logs in, SO gets access token
SO tries to use access token
SO gets back result + refresh token
SO saves refresh token
SO has permanent access to users Google account
In Google o-Auth , the access token will only be valid for 1 hour, so you need to programmatically update your access token in each one hour, simple you can create web api to do so,you need to have a refresh token, and also that refresh token will not be expired , using c# code, I have done this.
if (dateTimeDiff > 55)
{
var request = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/oauth2/v3/token");
var postData = "refresh_token=your refresh token";
postData += "&client_id=your client id";
postData += "&client_secret=your client secrent";
postData += "&grant_type=refresh_token";
var data = Encoding.ASCII.GetBytes(postData);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
request.UseDefaultCredentials = true;
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
string responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
}
you need to save the last updated date time of the access token somewhere(say in database), so that , whenever you have to make a request , so you can subtract that with current date time , if it is more than 60 minutes , you need to call the webapi to get new token .
We have a requirement where we want the ability to launch a new application (NewApp) from the base app (BaseApp). The login credentials for BaseApp and the NewApp are same since they use the same identity store.
BaseApp and NewApp are on different tech stacks, both being web applications. they are hosted on different servers.
NewApp provides basic Auth for web access.
How can I use java script to launch NewApp from links on my BaseApp. I want to be able to inject Basic Auth credentials.
I have tried using xmlHTTPRequest, but it appears to be geared more towards calling REST services and processing the fetched data rather than launching URLs.
using java script window.open(url) does not let me inject authorization header!
anything else I can explore ?
Additional Info:
The session cookie for BaseApp is not applicable for NewApp. When we first complete Basic Auth on the BaseApp, it generates a new Session Cookie which needs to be used.
Through xhr we are able to call the URL (with Basic Auth) for the NewApp. This returns a response with a session cookie.
The problem that I am unable to figure out is, how to render the response on a new window/tab on the browser.
The simplest solution is calling the new window with following url
http://<user>:<pass>#newapp
but this is not a good solution because you see credentials in plain text. You should better try one of the following solutions:
If you use a session cookie it's often possible to append it to the url like http://newapp?JSESSIONID=kjasbfkji877786sdf.
Either you can use the same Cookie from your base app (that should not be possible if the applications are on different machines)
Create XMLHttpRequest calling NewApp and authentication via BasicAuth. The response header should contain the required session cookie from the other server. Then you can call NewApp with the required cookie.
If you have no session cookie you could also try the second solution (XHR) because if a session is created it is also stored in the browser and the browser can manage session handling itself.
So in cases 1.2. and 2. you should do the following:
var XMLReq = new XMLHttpRequest();
XMLReq.open("GET", "url", asynchronous?, "user", "password");
XMLReq.send(null);
In case 1.2. you need to provide an additional handler for a succeeding call:
XMLReq.onload = function() {
window.open('http://NewApp?JSESSIONID='+XMLReq.getResponseHeader('Cookie'));
// note that 'JSESSIONID' depends on your backend implementation, this example is made for java backend
}
In case 2. it's simply
XMLReq.onload = function() {
window.open('http://NewApp');
}
The best solution would be OAuth or OpenID in this case but that depends on your usecase and your system environment and requires maybe a more complex backend implementation if there is currently no support for OAuth or OpenID.