Display content based on sign in state via OAuth - cookies

I have my api behind a third party OAuth (ex. google, twitter, etc). When a user hits /api/login, they are redirected into the OAuth flow and then sent back to my callback /api/login/callback. I store their login info and then send back a same-site http only session cookie to validate their user id. On subsequent requests, I retrieve that session cookie to get their user info and then perform requests using the OAuth token stored earlier.
Now, I want to create a frontend to go with my backend REST api. When a user goes to my / route they get a generic about page along with a sign in button. The sign in button redirects to my /api/login route and eventually back to my /api/login/callback. Now, the callback will redirect again to the / route. Subsequent requests made will have the session cookie attached and will go through.
My problem arises in that I don't know how to communicate to my frontend that my user is logged in. Because my session cookie is http only I can't access the cookie on page load to render a different UI for logged in users.
Some ideas I've had:
Hit up a /api/me URL that returns 401 or 200 depending on if the session cookie was sent. The problem with this is that this will leave the frontend in a limbo while the request is resolving.
Make the cookie not https only. However, I've read online that this makes it vulnerable to XSS attacks.
Send a second, non https only cookie as well to show that the session cookie exists. If this cookie is tampered with the worst that can happen is that a user will receive a 401 error later down the road when they make an API call without the session cookie.
Put something in local storage or in a cookie before the request to signify a user hit log in and check for it on page load. However, I won't know if the login succeeded or not.
Create a second page specifically for unsigned in users on the / route. Then, the callback can redirect to /signed-in. However, how will my /signed-in route know if a user navigated there or if the server redirected them? (ex. if the user autocomplete's the browser bar to the /signed-in route after their session expires)
Out of all these the third approach seems the most viable (the second cookie). However, this seems like a very trivial problem that someone has solved before. What am I missing here?
Note: I don't want to use ssr here. If I was using ssr I could simply just check for the session cookie server-side on the / route and reply with a different HTML template.
Edit: I could combine ideas 3 and 4. Put something in a store before sign in. If sign in fails, have my server redirect to a /fail page. If not, redirect to /. Then, / can reload the store on page load. /fail would also delete the stored item so that a user who failed can't just immediately go back to / and see they are logged in. The only unauthorized people who would see my user ui on / would be
Users who close out of the page during their login (never finish their login so never redirected to /fail to delete the store)
Users who revoke their OAuth token. This will have to be caught later down the road when my server receives a 401.
I could also add in a third "authorizing" state. I would set this before login. On page load with the authorizing state, I'd make a request in the background to validate that the user finished signing in. If I get a 401 from my server I'd have to move the user out of the authorized page. It wouldn't be nice but it'd occur less often than if I didn't use the store.

Before hitting sign in set a temporary loading value inside a store (cookie, framework store, localstorage, etc).
If the callback URL receives a failure value, redirect to the /fail route. /fail will set a failure value inside the store and redirect to /.
If the callback URl receives a success value, redirect to the /success route. /success will replace the temporary value with a success value.
On page load, read the store.
If the store is empty its a new user.
If the store has a temporary value, they never get redirected after the callback. Show a toast about an error and then display the sign in page.
If the store has a failure value, they failed the OAuth. Again, show a toast and display the sign in page.
If the store has a success value, everything went right. Show the user UI.
Eventually, the user may want to revoke their token. If they do, my app will not know until I make a request to a protected api endpoint with their token. If so, just pass the 401 to my frontend. I can show a modal saying they are unauthorized and then replace the success store value with an empty value.

Related

Handle redirection in async calls

I have an app where on initial load the user is redirected to sign in page. Once the user is authenticated, he is then taken to homepage. While verifying the user, an HttpOnly cookie is also set to the browser. So now to remove the hassle for user to login every time he refreshes the app or opens it in another tab. I'm sending a authenticate request back to the server inside beforeModel hook of my application route. This will verify the user and page loads as expected. However if the server response has 401 (either because user logged out or cookie expired) the app will redirect him to login page. Everything works fine and as expected.
But there are few things tricky to resolve.
If the user gives path as /login I need to wait for the authenticate request to complete before deciding on to render the login template or to redirect to home screen if he is already logged in.
Also wait for validate call to complete before executing the model hook in target url. I saw the modal request going to server even when the response was 401 for authenticate call.
A good example is in github page, where once you log in and go to /login page they take you to your home page.
After some experiment with routes.
I came up with this solution.
On every url request( either by refresh or entered path ), store the url unless it is for /login. This would be done in most likely beforeModel hook of application route.
Then on each topmost route of route hierarchy that an authenticated user can browse through have it redirect to login route.
On login route make the appropriate call to the API for authentication. If user is logged in transition to the stored url or to homepage if nothing is stored. If however 401 comes back load the template for the same.
Make sure that your async call for the API is made in one of beforeModel, model or afterModel hook, and you have the call to API in a return statment, otherwise the template will load no matter.
NOTE:
1. You can have the url stored in some service which will be accessible throughout app. You can also have the service store other login information for future use.
2. In point (2), make sure to do some check before redirecting to /login in case of unnecessary redirection. Suppose if the route was transitioned internally rather than through page load. 3. Don't forget to handle 401 case in your authenticate request, or it may just display nothing on page with an error in console.
Hope this helps

Should CSRF tokens be server-side validated?

First, I want to make sure I got the CSRF token workflow right.
The server sets a cookie on my machine, on the site's domain. The browser prevents access to this cookie from other domains. When a POST request is made, I send the CSRF token to the server that then compares it to my cookie. It they're not the same, a 403 Forbidden page is returned.
Now, if I manually change the value of the token in the cookie and send that new value in the POST request, should the server return a 403 or not? Does the server need to validate the token agains a value stored on the server or on the cookie?
I am using the default implementation of CSRF protection on Django 1.3 (https://docs.djangoproject.com/en/1.3/ref/contrib/csrf/) and it validates the token sent in the request against the token only.
How do you send the token?
Usually, the tokens should be some function (with a secret key - known only to the server; e.g., MAC) of the cookie! not the cookie.
Than the flow is as follows:
1. Client sends the server request with a cookie.
2. Server returns a web page with CSRF token(s) for different purposes (e.g., forms or just a simple get requests via the URL).
3. The client performs some action (via POST or GET) and sends request with the token (in the request body or in the URL) and with the cookie.
4. The server is stateless, but it can verify that the request was sent by the same client by calculating the function (with the secret key that the server knows) on the cookie (or on part of it), and comparing the output with the token.
In the case of CSRF, the cookie is automatically appended to the request by the browser, but the attacker (that probably even doesn't know the cookie) cannot add the corresponding tokens.
I believe you should do something like this.
Now, if I manually change the value of the token in the cookie and
send that new value in the POST request, should the server return a
403 or not? Does the server need to validate the token agains a value
stored on the server or on the cookie?
The server should be stateless (usually). You don't want to verify the token every request against some value in a database or something like that. It is better to verify against the cookie.
In that case, if you change the token, than it probably won't match the cookie, and you should send 403.
TL;DR: Yes, either you, or the framework you are using, needs to have server-side logic to validate a CSRF token. It cannot be a cookie, it has to be something that requires the user to be on your page, versus click on a link an attacker provides.
You've got the workflow pretty much correct. The first step is to generate a cryptographically random string that cannot be predicted by an attacker. Every programming language has its own construct to do this, but a 24 - 32 character string should be good to serve the purpose.
Before we get to the next step, let's make sure we know what threat we're dealing with - we don't want an attacker to make a request on behalf of the user, so there should be something that is accessible to the browser that requires the user to perform an action to send the token, BUT, if the user clicks on something the attacker has set up, the token should not be sent.
Given this, the one way this should NOT be done is using cookies. The browser will automatically send cookies every single time a request is made to the domain the cookie is set on, so this automatically defeats our defense.
That said, let's go to the next step, which is to set this token in a way that is verifiable by you on the server side, but not accessible to the attacker. There's multiple ways to do this:
1) A CSRF Header: This is done in many node.js/Express installations - the CSRF token is sent as a header, to be specific, a X-CSRF-Token header. After generating this token, the server stores this in the session store for that particular cookie. On the front end, the token is stored as a JavaScript variable, which means only requests generated on that particular page can have the header.. Whenever a request is made, both the session cookie (in the case of node.js, connect.sid) and the X-CSRF-Token is required for all POST/PUT/DELETE requests. If the wrong token is sent, the server sends a 401 Unauthorized, and regenerates the token, requesting login from the user.
<script type="text/javascript">
window.NODE_ENV = {};
window.NODE_ENV.csrf = "q8t4gLkMFSxFupWO7vqkXXqD";
window.NODE_ENV.isDevelopment = "true";
</script>
2) A Hidden Form Value: A lot of PHP installations use this as the CSRF defense mechanism. Depending on the configuration, either a session specific or a request specific (latter is overkill unless the application needs it) token is embedded in a hidden form field. This way, it is sent every time a form is submitted. The method of verification varies - it can be via verifying it against the database, or it can be a server-specific session store.
3) Double Submit Cookies: This is a mechanism suggested by OWASP, where in addition to sending the session cookies via the header, you also include it in the forms submitted. This way, once you verify that the session is valid, you can verify that the form contains the session variables also. If you use this mechanism, it is critical to make sure that you validate the user's session before validating CSRF; otherwise, it introduces flaws.
While building/testing this mechanism, it is important to note that while a lot of implementations limit it to POST/DELETE/PUT transactions, this is because it is automatically assumed that all sensitive transactions happen through this verbs. If your application performs sensitive transactions (such as activations) using GET, then you need this mechanism for GET/HEAD also.

Rails HTTP_REFERER is empty after paypal redirection.

I am building a rails paypal integration, I am ussing the sandbox
and after the purchace I am redirecting the user on a "Your transaction has been completed"
page.
I want to prevent though users to directly access the page.
So I am trying to trace HTTP_REFERER but is not been set
request.env.has_key?('HTTP_REFERER')
<http://stackoverflow.com/questions/3104711/ruby-on-rails-request-envhttp-referer-returns-nil/3104799#3104799>
I suggest doing it another way. Trusting the referrer is not a very secure way to prevent users from accessing pages as it can be easily manipulated by the user and it is unreliable.
Typically, if you're re-directing from HTTPS to HTTP, it will be empty as I suspect this is the case.
I would use either sessions or the PayPal token to prevent users from accessing the page. If an order session number is set, and payment is completed through PayPal (PayPal will send you details back that the payment is a success, token etc.), then display the payment completion page.

Django session cookie: from (any) other domain, check if user is logged in

I have a domain domain1.com. The user logs in and a cookie is set. This is done using Django sessions.
I then go to another domain domain2.com. This domain runs javascript. From this javascript, I want to see if the user is logged into domain1.com.
Is this possible? Can I see a cookie belonging to domain1 from domain2? Or can I somehow via ajax make a call domain1 to check if the user is logged in?
Also, the user might originally have logged into domain1 from Chrome, but now they are accessing domain2 from another browser. Aren't cookies browser specific?
EDIT:
The real problem I am trying to solve? (re comment below): I have created a Chrome extension. When the user presses the extension icon from domain2, a javascript is run, which collects information from the page. This information needs to be sent to the user's account on domain1. Note that domain2 can be ANY domain, not one that I have created.
What I tried with AJAX and cookies.
set cookie from domain1:
response.set_cookie("user_cookie", value="somevalue", max_age=60*60, expires=None, path='/', domain=None, secure=None, httponly=False)
Create Python function, which is executed from domain1.com/checklogin:
#csrf_exempt
def is_logged_in(request):
cookie = request.COOKIES.get('user_cookie')
if cookie is not None:
return HttpResponse("1")
else:
return HttpResponse("0")
Go to domain1.com/checklogin -> The response is "1"
Call javascript from domain2 as follows:
var xmlHttp_1=new XMLHttpRequest();
xmlHttp_1.open("POST","http://domain1.com/checklogin/",false);
xmlHttp_1.send();
alert(xmlHttp_1.responseText);
The response here is, incorrectly, 0. It does not see the cookie created by domain1.
Note that domain1 is, at this point, localhost and domain2 is a real domain. Could this be the issue? It does properly call the function.
Is this possible? Can I see a cookie belonging to domain1 from
domain2?
No. Cookies are restricted to domains (and their subdomains). A cookie for .foo.com is accessible to www.foo.com, zoo.foo.com but not bar.com.
Or can I somehow via ajax make a call domain1 to check if the user is
logged in?
This is one way, yes and it will work.
Also, the user might originally have logged into domain1 from Chrome,
but now they are accessing domain2 from another browser. Aren't
cookies browser specific?
Yes, they are. If you are logged into Chrome, and you open Safari, you won't be logged in.
cookies are domain specific, you may share cookies between foo.example.com and bar.example.com but not between two domains. For work around, you need to send ajax request from domain two to domain one and check there if cookie as set and send response back to domain two.
Check this So question for reference:
Setting default cookie domain for Django site with multiple domain names

Automatic cookie single sign on on multiple domains - like google

I don't understand how google achieve the following mechanism of single sign on:
I login in gmail for example (I suppose this creates a cookie withmy authorization)
I open a new tab and direct type the url of "youtube"
Then I enter youtube logged in.
How can this second site detect that I've already been logged in.
They are different domains. Youtube can't read the cookie of Gmail.
All the solutions I've read about Single sign on don't allow this. The client always ask permission to a central login app.
In my example YouTube doesn't know I am the same user logged in Gmail (actually it does know, but I don't understand how)
Note that I type the url of "youtube" by hand. I don't clic the youtube icon from the upper toolbar of gmail (In that case gmail may pass some auth params through the url for example).
The cookies are set on specific domains. Ex:
setcookie(name,value,expire,path,domain)
When you log in on gmail, before "mail.google.com", you have been redirected to "accounts.google.com" then to "mail.google.com" so the cookies are on "accounts.google.com" too.
In this case, the domain is "accounts.google.com" and the path is "/" (the home path).
When you request "www.youtube.com" then you click on "connection" it requests
"accounts.google.com" fast so you can't see this redirection and checks if you have cookies on "accounts.google.com". If so, it checks if the cookies are valid and not expired, or user not banned... Then it redirects you to "www.youtube.com/signin?loginthisSession=Sessionid". This request contains the value of the of sessionid cookie catched from the cookies of "accounts.google.com".
In the last step, "www.youtube.com" logs you and set its own cookie on the domain "www.youtube.com" and saves them.
So the trick is on the 302 HTTP redirect.
Update
i do not know why people keep mentioning iframe take a look at the date whene this questions was posted on 2016 google was not using then iframe as i mentioned the capture of web traffic as you can see SetSID wich means set the cookie of SESSION_ID from accounts.google.dz(com) then redirects to youtube.com it can not be used trought iframe differant domains security measure you can not be redirected from domain to domain trought iframe neither please read this before posting
Cookies and localStorage can be shared between domains using an intermediate domain. On the home page is embedded an "iframe ', which accesses cookies and sends messages to the main.
mail.google.com and youtube.com can share the cookies using accounts.google.es. Open Chrome->Inspect->Resources->Local storage and you will see in accounts.google.com the authentication token in JWT format.
I have detailed the technical steps in this answer: https://stackoverflow.com/a/37565692/6371459. Also take a look at https://github.com/Aralink/ssojwt to see an implementation of a Single Sign On using JWT in a central domain
Check this out.. http://www.codeproject.com/Articles/106439/Single-Sign-On-SSO-for-cross-domain-ASP-NET-applic.
The article consist explanation and sample of SSO cross domain.
As far as I remember, if I am not wrong, cookies contains a specified field that contains the domain that can read and get such cookie. That is made in order to prevent certain web sites to read all your cookie list and make your own business. You should be able to see which kind of sites can 'see' your gmail cookie.
Correct me if I am wrong, this should compile the answer given regarding the SID and gmail-YouTube example..
While evaluating this cross domain SSO topic, I have come up with possible a new SSO synchronization flow using cookie with timestamp. Although it is not a flow used by Google, I think this flow is possible to implement for system with limited number of domains.
This flow do not use 3rd party cookie
This is going to be a long post :)
domains
To make an example, let say we have these domains for our example pet forums:
https://account.domain1.com (For SSO Login)
.domain1.com (e.g. https://cat.domain1.com)
.domain2.com (e.g. https://dog.domain2.com)
.domain3.com (e.g. https://rabbit.domain3.com)
Change to https://account.domain1.com:
Add https://account.domain2.com and https://account.domain3.com, route both host name traffic to the server hosting https://account.domain1.com
Login Steps:
User go to dog.domain2.com, user have not sign in yet.
User click the Login button in dog.domain2.com
User get redirect to account.domain1.com for login
This step can be any Login protocol, OAuth, OIDC, SAML, CAS, etc
So, it is important for user to be redirected back to original page after login
Let say this https://account.domain1.com?redirect_uri=https://dog.domain2.com
redirect_uri as in the URL to go back after login success
User Input username & password, login success
New step, before redirect back to https://dog.domain2.com, set cookies on all domains
Redirect browser to https://accounts.domain2.com?...
Set a cookie on the .domains2.com domain (More on the cookie value later)
Redirect browser to https://accounts.domain2.com?...
Set a cookie on the .domains3.com domain
Redirect browser to https://accounts.domain1.com?...
Set a cookie on the .domains1.com domain
Redirect back to original flow
Redirect user back to their original service, i.e. https://dog.domain2.com
Now, right after login flow we have cookies over all 3 domains. Any of our service (e.g. https://cat.domain1.com / https://dog.domain2.com / https://rabbit.domain2.com ) can access this cookie under their own domain.
Cookie Content
The content of the cookie, should allows for any webpage to look at it, and determine if SSO sync is needed
Different types of cookie content can be stored, including
Boolean indicate user logined or not
User ID
Expired At timestamp
Boolean indicate user logined or not
Storing have_user_login = true / false have sync issue
Suppose User A login, visit https://cat.domain1.com, User A Logout, and User B login
Now, from https://cat.domain1.com standpoint, no sync is needed
However, https://cat.domain1.com is storing User A instead of User B, hence the sync issue.
User ID
While it is tempting to just stored the user_id on those cookie, and let all the domain to see them and set the user accordingly.
This is way too dangerous, since the cookie is set at the parent domain,
if any of the website under your domain been hacked, impersonation might happen (Copying any of the user_id, pasting it to their own browser cookie).
Expired At Timestamp
What I suggest, is for the cookie value to set as the SSO expired time, and set the type as session cookie, this have the following benefits:
An expired time have minimal security impact if leaked / altered
Our website can check the expired time to know if user need to relogin
As for why session cookie, is for when user close them browser, and tried to login again, the cookie will be deleted hence logout the user as well
Any webpage that use the SSO, should also stored a cookie themselves with the same expired time
There will be cases that, User A Login, visit https://cat.domains1.com Then User B Login
Since User A and User B will have a different login expired time, storing and compare that timestamp will tell the user to sync with SSO again
Example checking implement for your service
E.g. On https://cat.domains1.com, you can add this to the top of your page load
<?php
$sso_expired_time = $_COOKIE["sso_expired_time "] ?? 0;
$website_expired_time = $_COOKIE["website_expired_time "] ?? 0;
if( (int) $sso_expired_time < time() || $sso_expired_time !== $website_expired_time ) {
// User not sync, perform sync
setcookie("website_expired_time", $website_expired_time,0,"/", $_SERVER['SERVER_NAME'], true, true);
// Redirect to https://account.domain1.com for Login
// Or, Initiate the login sequence for your selected login protocol
header("Location: https://account.domain1.com/.....")
exit;
}
// User is sync
// Page load success, continue other operation
Logout
Login is very similar to Login, basically:
Before logout goes through, redirect to all 3 domains just like login
Remove the SSO cookie
Continue the normal logout flow
Pro and cons for the methods:
Pro: All domain sync possible
Pro: No need to relies on 3rd party cookie
Cons: First time login longer (around 50ms longer)
Cons: Customization on every website is needed for the sync to works