tl;dr -- Is there a kind of response I can send to a browser's POST request (from my web server running Node.js/Express.js) that will prevent the browser from navigating to a new page and breaking any open WebSocket connections?
I am implementing the new Sign In With Google OAuth flow from a pure client-sided React app that uses WebSocket for bi-directional communication.
The new Sign in With Google flow uses either a pre-loaded HTML or JavaScript component to render a button for logging in. Clicking the button opens a new window where the user can consent to sharing data and either log in with a Google account or select an existing logged-in account.
Once complete, the Sign-In With Google flow will provide an identity token either via a callback function or a POST request to specified URL. I want to use the second approach to POST the token to a https://my-server/googleLoginComplete endpoint. The endpoint will perform some internal login and session operations, and then communicate to the browser/client via an open WebSocket connection that the login operation is complete.
I have everything working, except for one issue. Google's API is performing a form POST to send the identity token to the developer supplied endpoint, and any response that I return from that endpoint will cause a navigation change in the browser which breaks the web socket connection.
If I had control over the Sign In With Google API, I could do the equivalent of a form.submit.preventDefault() and instead just use a fecth or xmlHttpRequest to perform the POST without navigating to a new page. Unfortunately, Google does not provide this or a similar option, nor do they even have the source code for their new client publicly available.
I've tried sending a 204 and 304 response, an empty object, a generic 200, sending a redirect back to the same URL the browser is already on, not sending anything back at all, destroying the resp completely, and some other variations. But I can't find anything that will simply result in the browser doing nothing. And the web socket connection remains open.
Here is some skeleton code:
// React: '/login.js'
export default function Login() {
// ...
return (
<Row>
<div id="g_id_onload"
data-client_id="my-client-id"
data-login_uri="http://localhost:5000/googleLoginComplete"
data-auto_prompt="false">
</div>
<div className="g_id_signin"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left">
</div>
</Row>
)
}
// Node.js/Express.js: 'app.js'
//...
app.post('/googleLoginComplete', (req, res) => {
const googleIdentToken = req.body.credential;
const sessionId = doSomeInternalSessionStuff(googleIdentToken);
socket.emit('login.success', {sessionId});
// Something here to just cause the browser to do nothing
res.send('ignore this and dont do any navigation');
});
(I know that I can change the Sign In With Goole method to use a callback instead, but to keep this consistent with other federated auth providers, I want to use the URL redirect option).
I am using .NET Core (+IdentityServer) backend along with the SPA React application.
Whenever I change the page inside my SPA application the url updates. (for example: / to /users).
The issue:
Whenever I refresh the page with F5 or want to go to the specific component connected to route using url only I get the following action inside my browser:
Basic state when pressing enter in the url: localhost:3000/users
After pressing enter: http://localhost:3000/signin-oidc#id_token=...
When the user is logged in the page redirects to the main url: localhost:3000/
So, basically I am always redirected from the current url to the main one.
How can I make the application redirect back to the localhost:3000/users on the refresh or url change using oidc-client in react?
What is the best practice to allow user to be able to refresh the page (or go to the specific url) keeping in mind that the user is already authorized?
If I understand you right and you are using the oidc-client-js library to implement Open Id Connect then my NodeJS Code Sample may be useful to you, and the Authenticator class in particular.
RESTORING THE PRE-REDIRECT LOCATION / DEEP LINKING
This is managed via code like this, which is also useful if the user has bookmarked a location within your app and needs to login first:
// Store the state when redirecting the page
await this._userManager.signinRedirect({
state: location.hash,
});
// Restore it afterwards - and remove tokens from the URL
const user = await this._userManager.signinRedirectCallback();
history.replaceState({}, document.title, user.state.hash);
RELOADING THE PAGE OR OPENING A NEW TAB
This is best managed via silent token renewal using the Authorization Server session cookie, so that there is no top level redirect.
await this._userManager.signinSilent();
RUNNING MY SAMPLE AGAINST YOUR SYSTEM
It may help you to have something to compare against, by running the sample against your own system, by changing the details in the SPA and API config files to point to Identity Server.
I have cable internet which requires me to login though a web page. It's annoying how it resets every day at 8 and 12am. I wanted to write a python script which will automate the login process. I've read many StackOverflow solutions so far, nothing has worked. I have tried Requests, Twill, RoboBrowser etc.
Upon inspecting the page source I came across a doLogin() ajax script, which is triggered by login button. Here is the full page source.
following is one of my implementations which fails
import requests
# Fill in your details here to be posted to the login form.
payload = {
'action': 'http://10.10.0.1/login',
'actualusername': 'username',
'actualpassword': 'password'
}
# Use 'with' to ensure the session context is closed after use.
with requests.Session() as s:
p = s.post("http://103.251.83.134/captiveportal/Default.aspx", data=payload)
# print the html returned or something more intelligent to see if it's a successful login page.
print p.text
# An authorised request.
#r = s.get('http://www.google.com')
#print r.text
EDIT: Solution
I used Selenium WebDriver to fix this. Check answer.
Use Selenium :) Download ChromeDriver to the path, make a two-time variable and check the time every minute. If it's 'login time', your browser will pass through the authorization.
from selenium import webdriver
def Authorization_for_broadband():
driver = webdriver.Chrome("C:\YOURPATHTO\CHROMEDRIVER.EXE")
driver.get('http://10.10.0.1/login')
driver.find_element_by_xpath('//*[#id="username"]').send_keys('USERNAME')
driver.find_element_by_xpath('//*[#id="password"]').send_keys('PASSWORD')
driver.find_element_by_xpath('//*[#id="btnLogin"]').click()
driver.close
while(1):
if time=='your-login-period1' or time == 'your-login-period2':
Authorization_for_broadband()
Your URL may be wrong. Looking at the source code it looks like the HTML form is posting data to the http://10.10.0.1/login page and then the doLogin() function is submitting data to Register.aspx?CheckCustomerStatus=1.
Also your payload includes the variable action and you're using a Session object, which I don't think is necessary.
I can't test it since it's a local login page I can't access, but I would try modifying your code to submit the login info to both pages using a simpler POST request
Here is my problem :
I'm creating a web app with loopback (great framework :) ) with an AngularJS client, everything works well but impossible to reload the page without being disconnected. This behaviour is normal, however I would like to persist the session with a "Remember me" checkbox and just avoid to be disconnect on page reload. The access token is stored in localStorage, I think I have to create a cookie on myself but the main point, how do I avoid disconnection ? And redirection to forbidden page. Then should I do this on server-side or client-side both ? I'm lost actually...
I did start from this application on github if you wish to have a better idea of the project:
https://github.com/strongloop/loopback-getting-started-intermediate
You shouldn't need to create a separate cookie to store the authToken if it's already in localStorage. The part you will need to modify is inside app.js, the .run() block, which checks for the existence of $rootScope.currentUser to determine if you're logged in, which will not be persisted across browser reloads.
The code creates $rootScope.currentUser in Auth.js. The .run() block of app.js simply checks for the existence of $rootScope.currentUser to determine if you are logged in.
So you'll need to change how you detect logged out state from simply checking for $rootScope.currentUser to attempting an actual call to User.getCurrent() or something. If you are logged in, the call will include the auth token in the headers, if not, you'll get a 401 status code response and you can redirect to the login page in that case.
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.