http basic authentication "log out" - javascript

HTTP basic authentication credentials are stored until the browser is closed, but is there a way to remove the credentials before the browser is closed?
I read about a trick with HTTP 401 status code, but it seems to work not properly (see comment to answer). Maybe the mechanism trac uses is the solution.
Can the credentials be deleted with JavaScript? Or with a combination of JavaScript and the status 401 trick?

Update: This solution does not seem to work anymore in many browsers. Kaitsu's comment:
This solution of sending false credentials to make browser forget the correct authenticated credentials doesn't work in Chrome (16) and IE (9). Works in Firefox (9).
Actually you can implement a workaround by sending false credentials to the service. This works in Browsers by sending another (non-existent?) Username without a password. The Browser loses the information about the authenticated credentials.
Example:
https://www.example.com/ => Log in
with basic auth as "user1"
Now open
https://foobar#www.example.com/
You're Logged out. ;)
Regards
P.s.: But please test this with all needed Browsers before you rely on the given information.

Expanding on Jan.'s answer, and updating owyongsk's answer:
Here is some example jquery java-script code to cause the browser to essentially send a bogus login request to the page your trying to protect, which in all tested browsers caused the cached credentials to be removed, then redirects the user to a non-protected page.
The alert() when something goes wrong should probably be changed to something else.
//Submits an invalid authentication header, causing the user to be 'logged out'
function logout() {
$.ajax({
type: "GET",
url: "PUT_YOUR_PROTECTED_URL_HERE",
dataType: 'json',
async: true,
username: "some_username_that_doesn't_exist",
password: "any_stupid_password",
data: '{ "comment" }'
})
//In our case, we WANT to get access denied, so a success would be a failure.
.done(function(){
alert('Error!')
})
//Likewise, a failure *usually* means we succeeded.
//set window.location to redirect the user to wherever you want them to go
.fail(function(){
window.location = "/";
});
}
Then it was as easy as just having the logout link call the logout() function, and it seemed to work seamlessly to the user, though it is still technically a hack job.

You can try a hack that is working at the moment with the latest Chrome and Firefox. Create a "/logout" page on your server which accepts only a certain credential such as username: false, password: false. Then using this AJAX request below, you can send the user to that page.
$("#logout").click(function(e){
e.preventDefault();
var request = new XMLHttpRequest();
request.open("get", "/logout", false, "false", "false");
request.send();
window.location.replace("WHEREVER YOU WANT YOUR LOGGED OUT USER TO GO");
});
What happens is that the false username and password is cached from the valid XMLHttpRequest instead of the current user's credentials, and when a user tries to login into any page, it will use the cached fake credentials, failing to authenticate, it will ask the user to enter another one. Hope this helps!

just finishing an implementation that worked fine to me:
At the server I evaluate Session, User Name and password, so I keep track of that information, the login algoritm is as follows:
1.Check if user and password is not empty, else return 401.
2.Check if we have registered the session in our logged-in user list, if not then check if user and password is valid and if so save session id in our list, then return 401.
I'll explain this step: if the session id is different one of three things happened:
a) The user is opening another window.
b) The user session has finished, ie user logged out.
c) The session expired due to inactivity.
But we want to save the session as long as the user credentials are valid but return a 401 to ask once for password, if we don't save the session then the user could never log in because we don't have the new session id in our list.
3.Check if user credentials are right, if so, save session info and continue serving pages, else return 401.
So, the only thing I have to logout a user is to close the session at the server when the user requests the logout page and the web browser shows again the login dialog.
I'm thinking as I write this that there has to be a step where the program checks if the user is already logged to avoid impersonation, maybe I can save more than one session id per user to allow multiple session, well, I would like your comments about it.
Hope you get the idea, and comment if you see any security flaw ;)

You can delete credentials with JavaScript:
$("#logout").click(function(){
try {
document.execCommand("ClearAuthenticationCache");
window.location.href('/logout.html'); // page with logout message somewhere in not protected directory
} catch (exception) {}
});
This code works only in IE. This is the reason why try/catch block is added there.
Also, for the same reason the logout link you should show for IE users only:
<!--[if IE]>
<div id="logout">[Logout]</div>
<![endif]-->
And for other users my suggestion is something like:
<div id="logout2" onclick="alert('Please close your browser window to logout')">[Logout]</div>

If you have control over the server code, you can create a "logout" function that replies "401 Unauthorized" regardless of the credentials given. This failure forces browsers to remove saved credentials.
I just tested this with Chrome 34, IE 11, Firefox 25 - using Express.js server and HTTP basic authentication.

What has worked for me in Chrome (Version 66) is to send an Ajax request to an URL that returns 401. That way the basic authentication cache seems to be cleared.
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/url_that_returns_401", true);
xhttp.send();

This codes worked for me in Chrome (version 83):
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/url_that_return_401", true);
xhttp.setRequestHeader('Authorization', 'Basic ');
xhttp.send();
Need to execute this code (maybe by button click or console window) and then prompt appeared, you need to press Cancel button. Then authentication fields clears and you can refresh page and login in with another credentials.

Mark the nonce as invalid. I did an Ajax call to the server side asking to logout. The server side gets the "Authenticate: Digest..." request in a header. It extracts the nonce="" from that line and adds it to a list of disabled nonces. So next time the browser sends that a request with that "Authenticate: Digest..." line the server replies with 401. This makes the browser ask the user for a new userid/password.
Works fine for my application but only for Digest authentication.

Related

How to revoke Identity API Token ( Chrome Extension )

I would like to add a Sign In with Google and a Sign Out button to my Chrome extension.
One technique that uses chrome.identity.getAuthToken for Sign In is described in this tutorial. It works great! When the button is clicked, it shows a popup for authentication and authorization.
But how should I implement the Sign Out button?
I tried to use the removeCachedAuthToken method in the on-click handler of my Sign Out button. With this, the sign-in functionality doesn't work as expected. After, when I pressed the Sign In button again, I got a new token directly without a popup asking the user to authenticate and authorize my extension. I would like users to be able to change their account by signing out. With this technique, that's not possible. How should I implement the sign out functionality to allow for this?
This has been bugging me too, until I realized that I got mixed up by the difference between sign-in and authorization, sign-out and revoking access.
First, let's not get caught up in the name of the button. Yo may call it Sign Out, but what you actually want to achieve is to let users revoke access for their Google Account, and then log in and grant access to a different account.
If you use removeCacheAuthToken, then authorize again, and see no popup, then that means the extension still has access to certain APIs. To check which apps have been granted access to which Google services, go to permission settings and have a look.
There are several ways to revoke access:
Go to chrome://identity-internals/ and remove the tokens that you want. Then click on the Authorize button, and you should see a popup to choose the Google accounts to grant access.
Of course, that method is for testing only. Your end users won't see the access token for your extension if they visit that page.
When the user clicks on the Revoke access button, or whatever name you call, display a popup that tells them to go to the permission settings page to manually revoke access.
Create a form on the current web page, add access token to the form and submits to the https://oauth2.googleapis.com/revoke endpoint.
From my experience, method 3 seems like an ideal solution, but it was a hit and mix for me. Sometimes I would get an invalid or expired token error, and troubleshooting it is not worth it. I would stick with method for peace of mind.
As mentioned in this answer, you can use https://accounts.google.com/o/oauth2/revoke?token=" + current_token) to allow the user to revoke access to the api.
Below is the function for the same:
function revokeToken() {
user_info_div.innerHTML = "";
chrome.identity.getAuthToken({ interactive: false },
function (current_token) {
if (!chrome.runtime.lastError) {
// #corecode_begin removeAndRevokeAuthToken
// #corecode_begin removeCachedAuthToken
// Remove the local cached token
chrome.identity.removeCachedAuthToken({token: current_token}, function(){});
// #corecode_end removeCachedAuthToken
// Make a request to revoke token in the server
var xhr = new XMLHttpRequest();
xhr.open(
"GET",
"https://accounts.google.com/o/oauth2/revoke?token=" + current_token);
xhr.send();
// #corecode_end removeAndRevokeAuthToken
// Update the user interface accordingly
changeState(STATE_START);
sampleSupport.log("Token revoked and removed from cache. " +
"Check chrome://identity-internals to confirm.");
}
});
}

How to implement username/password auth with express ntlm?

I am trying to implement username and password auth with Express-ntlm.I have added below code as middleware
app.use( ntlm({
domain: '<domainname>',
domaincontroller: '<ldap url>',
}));
I am not providing any user name password in the request and its automatically adding ntlm header in the request with my loggedin user details. I am getting {Authenticated:true} along with my username and system name in request.ntlm.
How can I provide different username/password in this and authenticate?
Also will this work if I login from linux/mac system?
If you use a supported browser (e.g. IE) NTLM will automatically log the user in using the current session in Windows. express-ntlm will just receive this information and exposes it to the application.
If you want to use other credentials you have to log in using a different user in Windows or use a browser that will show you a prompt for username and password if it gets an NTLM challenge. If I remember correctly Chrome will do it like this.
I never tried it using macOS/Linux, but I'm sure most browser will just provide you a username/password prompt.

Webdriver.IO Dealing with Single Sign On cookies

I'm creating some node.js webdriverio tests. When I connect to any of my sites, there's a check for the presence of a ssosession cookie (among others) to confirm user has access to the page. If it's not there, the browser immediately redirects to our login page. I need to capture the resulting cookies from a successful login and use them to load the pages I want to test.
In curl, I do that like this:
curl -H"Content-type: application/json" --user-agent "MyTestAgent" --cookie-jar cookiefile -o - --data-ascii "{\"user_id\": \"username\", \"user_secret\": \"password\"}" -X POST https://loginpage.net
Then:
curl -H"Content-type: application/json" --user-agent "MyTestAgent" --cookie cookiefile - --cookie-jar cookiefile -o - https://testpage.net
Currently in my code, when I hit the url of the page I intend to test it immediately redirects to the login page, I enter the login credentials and it logs in successfully. I then dump the cookies out to console and they're not updated. The ssosession stuff isn't there (yet?). If I console.log the ssosession alone, it's null. If I console.log all the cookies, they're the same as if I just grabbed the cookies from the login page without signing in. None of the SSO stuff is present.
describe('testpage.net page', function () {
it('should login', function () {
browser.url('https://testpage.net');
//this immediately redirects to https://loginpage.net
browser.setValue('#userid','noone#nowhere.net');
browser.setValue('#passwd','S3cr3tP4ssw0rd');
browser.submitForm('#login');
console.log(browser.getCookie('ssosession'));
//returns null
console.log(browser.getCookie());
//returns cookies that existed prior to login success
});
});
I need a means to grab the post login success cookies, store them locally equivalent to curls cookiejar and use them in subsequent tests. I've been beating my head up against a wall trying to figure out how this is done. This is an internal site that has very dated authentication method, but I need to write test automation for it's gui. Once over this particular hurdle, it should be easy to do that. Suggestions greatly appreciated!
I had the same problem and found a workaround solution.
let's say we have two domains: sso.example.com and application.example.com
After successful login, I open a new popup window with a url of the SSO, and grab the cookies from this popup, and store them to a file.
when the test script starts I am just injecting the cookies and everything works perfect :-)
// login just ended, you are at "application.example.com/sessioned-url"
// ...
// post login code, save cookies to file
await saveCookies(browser);
//open popup with sso.example.com (any url of this subdomain is OK even favicon or 404 becasue the cookies will be available)
await browser.newWindow("https://sso.example.com", {
windowName: "WebdriverIO window",
windowFeature: "width=1,height=1,resizable,scrollbars=yes,status=1",
});
const handles = await browser.getWindowHandles();
//move to the new window the SSO url
await browser.switchToWindow(handles[1]);
// save the SSO cookies (make sure to add the cookies to a file, NOT override it)
await saveCookies(browser);
//close popup and go back to "application.example.com/sessioned-url" window...
await browser.closeWindow();
await browser.switchToWindow(handles[0]);
when starting a new session:
// inject cookies of both domains
await injectCookies(browser);
// main window, will goto url without forcing login
await browser.url("application.example.com/sessioned-url");
hope it will help anyone in the long future :-)

Check if user posted (or cancelled) sharing to Facebook in Share Dialog using FB.ui

I'm using a share dialog and I want something to occur after the user posts something to his/her timeline. Right now I'm using:
function shareToFB(myName, myLink) {
$('.overlay-bg').hide();
FB.ui({
method: 'share',
href: myLink,
name: myName
}, function(response) {
if (response && !response.error_code) {
alert("Something");
} else {
alert("Error");
}
}
);
}
But this causes "Something" to show up even when the user cancels posting. Is there any way I can find if the user has posted the message to his/her timeline without requiring any permissions. I won't mind if this requires me to use a different method of sharing (like a feed dialog).
Use Feed Dialog instead.
Despite its UI being ugly, successful sharing via Feed Dialog will return a response object like {post_id: "10206702999763274_10206703017843726"} regardless of user authenticating your Facebook app or not, while pressing cancel will return a null and closing popup will return undefined.
You can test this behaviour by going to your normal Facebook user profile settings and try removing your app from allowed list.
This is in contrast to the Share Dialog, which, if your app has not been authenticated by user, will return an empty response object {} regardless of successful sharing or not.
Furthermore, if you use the direct url redirect method instead of js SDK, when the user press cancel on the share page you will receive a GET parameter error_code and error_message appended to your redirect target.
[From December 2015] => Use Feed Dialog or the Share Dialog to access the post ID in the response. To achieve this the user must have logged into your app and granted it publish_actions. https://developers.facebook.com/docs/sharing/reference/share-dialog#response
NOTE: This is true for the time of this writing (API v2.3). However as Facebook is notorious for breaking API behaviour, you should test it yourself first.
According to the docs, the response is the object_id of the posted entry - and it is only filled if the user authorized the App. Meaning, you canĀ“t detect if a user really posted something if the user is not authorized.
Source: https://developers.facebook.com/docs/sharing/reference/share-dialog

FB.getLoginStatus returns status unknown

When calling FB.getLoginStatus using a valid Facebook App the response status is always unknown. Exact response is {authResponse: undefined, status: "unknown"}.
<html>
<body>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function(){
FB.init({ appId:'1484134468466778', status:true, cookie:true, xfbml:true});
FB.getLoginStatus(function(response){
console.log(response);
});
};
(function(d){
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document));
</script>
</body>
</html>
Example URL:
http://media.tuentifotos.com/simple_test.html
Here a screenshot of the Facebook App Settings.
This was happening for me in Chrome, and was because Chrome was configured to block third-party cookies and data.
Once I made that configuration change, FaceBook is able to log me into my app without a problem.
Chrome Settings
Show advanced settings...
Privacy
Content settings...
uncheck Block third-party cookies and site data
I too faced this problem in Chrome. However, in Firefox it worked as expected with the status returned as connected when the user had logged in previously.
I found a clue about this from an answer to the similar question here.
The root cause of this issue is, on FB.logout(), Chrome is not removing the cookie fblo_<your-app-id> which is somehow affecting FB.getLoginStatus() function to return unknown
Fix: On calling FB.logout(), you may programmatically delete the cookie fblo_<your-app-id>
FB.logout(function(response) {
deleteCookie("fblo_" + fbAppId); // fblo_yourFBAppId. example: fblo_444499089231295
});
function deleteCookie(name) {
document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
When I checked, the status is showing "not_authorized" and that's fine, since I've not authorized the app yet.
To complete the flow, you should add the FB.login whenever user id is not authorized or not logged-in to facebook:
window.fbAsyncInit = function(){
FB.init({ appId:'{APP-ID}', status:true, cookie:true, xfbml:true});
FB.getLoginStatus(function(response){
if (response.status === 'connected') {
//proceed
} else if (response.status === 'not_authorized') {
login();
} else {
login();
}
});
};
function login(){
FB.login(function(response) {
if (response.authResponse) {
// proceed
} else {
// not auth / cancelled the login!
}
});
}
For me this meant "on my login page" I needed to specify cookies.
window.fbAsyncInit = function() {
FB.init({
version: 'v2.8',
cookie : true,
});
};
But don't ask me why this was the case. It also fixed it needing to click twice on the login button to actually login, and doesn't even require an appId, seemingly, FWIW...
The final answer
Ok so I think I've finally figured this damn issue out.
What you need to know:
1) You authenticate with Facebook via an app ID. This sets various cookies, where your app ID is tagged on the end:
fblo_000000000000
fbm_000000000000
fbsr_000000000000
2) If you delete these cookies, you're still authenticated to facebook as a regular user (unless you log out completely). And on Facebook's servers they still know you are authorized on this app.
So when you run FB.getLoginStatus() again it will just recreate them and put them back again. This is not what your user expects. This is bad. They clicked 'Log out'.
3) The docs explicitly say this:
The user is either not logged into Facebook or explicitly logged out of your application so it doesn't attempt to connect to Facebook and thus, we don't know if they've authenticated your application or not. (unknown)
So they don't even TRY to check if this cookie is set. That's why you get null or undefined. So the fblo cookie is considered like an 'opt-out'. You're NOT ACTUALLY LOGGED OUT by what any regular person would consider being logged out as to mean. There's just a cookie saying you are! Of course if the user logs back in with the login button then the cookie will be deleted, which is what you and your user wants.
Therefore I believe the only thing that makes sense to do (if you truly need to know the user's status) is to:
Manually check the existance of fblo_<APPID> cookie before you run FB.getLoginStatus.
If the cookie doesn't exist then do nothing and run your normal flow.
If the cookie does exist you have several options :
1) Option 1
Do absolutely nothing. You now understand the issue, you understand not to delete the cookie and perhaps you don't need to do anything other than show the Facebook login button.
2) Option 2
You assume the person is a user of your app, and do whatever you need to do to show the UI. But you won't be running your full normal logic - this will be application specific to you .
3) Option 3
Manually set the cookie value fblo_<APPID> to n (instead of 'y'). Remember this cookie is set on your domain so you're allowed to change it. *Depending upon your development environment this will vary, but it needs to be done client side, and you may need to specify path of `/' and your cookie domain).
Run the getLoginStatus(..., true) - it will not be blocked now because the cookie is now n. However you must not run your normal logic here - because all you want to do is check if the user is actually a Facebook user and/or still authenticated with your app.
Manually set the cookie value back to y
Unfortunately I can't recommend this solution due to weird race conditions. It almost almost works, but FB could deliberately or accidentally break it at any time - and it actually ends up still getting confused thinking you're really logged in when you aren't. Plus it could have all kinds of complications with other plugins I haven't even observed.
I wish the getLoginStatus would just always run and give accurate information, but this is the only true solution I see. And it's been this way for 8 years so I think we're stuck with it.
4) Option 4 - Set your own cookie
You always have the option to set your own cookies to indicate certain things, like 'this user has at some point logged into Facebook' or 'this user is authenticated with my app'. But I'm cautious about doing this because it can quickly get complicated.
Incidentally I revisited this issue because I'm using the Messenger plugin for my website and I don't want it to display at all unless I know the person has a Facebook account. So I need an accurate response to login status.
This was happening to me until I turn on my browser to allow third-party websites to save and read cookies.
To do this go to
settings > advanced > site settings > Cookies and site data
Then uncheck the option blocking third-party websites from saving and reading cookies.
This might not be a good option but it solves these issues for me.
You just need make your site URL from http to https
DO NOT use http
I had the same issue in IE. Flimzy's answer got me thinking. I tried running IE as administrator and it worked.
I had the same problem, I fixed this by clearing all cache and cookies.
You have to test it from the live domain (which you provided in the app).
I had the same problem when testing locally (using local domain).
In my case I was using Brave browser which didn't allow cookies due to which it was giving status unknown. I switched to chrome and now it is working.

Categories