I have a page that can be viewed authenticated or not.
This page contains form (with its CSRF token)
The form requires authentication to be taken into account
I manage the authentication check by ajax (on form submit)
If the user is not authenticated, he can do it from a new window (with a link)
Once authenticated, the user can close the new window and resubmit the form
In that case, Django tells me that my CRSF token is not valid anymore
CSRF token missing or incorrect
I imagine that's because the session_id has changed or something like that.
Is my assertion correct ?
How could I allow the user to resubmit the form without having to reload the page ?
While the above method can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many javascript frameworks provide hooks that allow headers to be set on every request.
You need to send the CSRF token through an AJAX call:
$.ajaxSetup({
beforeSend: function(xhr, settings) {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
// Only send the token to relative URLs i.e. locally.
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});
Here is reference link, https://docs.djangoproject.com/en/1.6/ref/contrib/csrf/#ajax
Related
I'm trying to do a cross origin login request and everything except logging in is working. So if I go to api.example.com and log in with the laravel app and then go to www.example.com I can get the user by calling the /api/user end point (setup as part of airlock). What I'm failing to do is log in via AJAX with username / password. Here is the JS used:
First I call setCSRFCookie()
function setCSRFCookie() {
let xhr = new XMLHttpRequest();
xhr.open('GET', domain+'/airlock/csrf-cookie');
xhr.withCredentials = true;
xhr.send(null);
}
Then I call loginUser()
function loginUser() {
let xhr = new XMLHttpRequest();
let params = 'username=m#example.com&password=password';
xhr.open('POST', domain + '/login', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
}
xhr.send(params);
}
I'm getting a 419 (unknown status) which appears to be Laravel's response for an invalid CSRF token. I looked at the actual request and both APP_NAME_session and XSRF-TOKEN are being passed as part of the request. The only thought I had was that the /login default auth route isn't setup for this and I need to do one under /api/login so it's served by the airlock middleware. I appreciate any help you can give.
Laravel 6.12.0
Airlock 0.1.0
Make sure api.example.com is included in config/airlock.php like this
'stateful' => [
'localhost',
'api.example.com'
],
And request middleware should be auth:airlock
I'm working with Spring Security and use JWT as access token , When a client sends the access token to server I must change an additional information (metadata) of this token and return a new one.
How can I achieve that ?
i try with this code but not working
String authorization = Context.getHeader("Authorization");
if (authorization != null) {
String tokenValue = authorization.replace("Bearer", "").trim();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
accessToken.getAdditionalInformation().put("activeProfileId", defaultProfileId);
return accessToken.getValue();
}
return null;
You should get your metadata ("claims") from the token, then add them to a new JWT builder that will return a new token. The new JWT must be entered in HttpResponse to forward it to the client. Instead, the client will have to implement an interceptor to retrieve it in a comfortable and transparent way.
You've to get all Additional Information as HashMap and place them in OAuth2Authentication. stackoverflow.com/a/19057480/11951081
In ajax should be:
https://api.jquery.com/category/ajax/global-ajax-event-handlers/
$.ajaxSetup({
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', <Jwt>)
},
success:function(event,jqXHR,ajaxOptions,data ){
console.log(ajaxOptions.getResponseHeader('Authorization'))
}
})
Is it safe to store the access token in a cookie?
I've checked, and even if you have the access token, if you are not properly logged in, you will get a 401.
The access token is changing (I'm changing it) every time the user logs in.
The cookie gets destroyed on sign out or on timeout (based on the rememberme option).
Should I anyway store it somewhere else? Where about?
Yes it's safe, but still you need to add middleware on your routes
Before we are using JWT AUth, this is our solution on frontend side on login page
axios({method: 'post',
data: {email: this.email.trim(),
password: this.password},
url: '/api/login'})
.then(res => {
if (res.data.success) {
// Sets the Cookie Expiration Time to 1 Hour same as JWT Token on the Backend
var now = new Date();
now.setTime(now.getTime() + 1 * 3600 * 1000);
document.cookie = "ut=" + res.data.data.type;
document.cookie = "api_token=" + res.data.data.token;
document.cookie = "expires=" + now.toUTCString() + ";"
// Redirect to a new URL, or do something on success
window.location.href = "/dashboard";
}
}).catch(function (error) {
//error
});
Any suggestions?
How should client know that access token has expired, so that he makes a request with refresh token for another access token?
If answer is that server API will return 401, then how can API know that access token has expired?
I'm using IdentityServer4.
Your api should reject any call if the containing bearer token has already been expired. For a webapi app, IdentityServerAuthenticationOptions will do the work.
But your caller Web application is responsible for keeping your access_token alive. For example, if your web application is an ASP.Net core application, you may use AspNetCore.Authentication.Cookies to authenticate any request. In that case, you can find the information about the token expiring info through OnValidatePrincipal event.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
//Get the new token Refresh the token
}
}
}
}
I have added a full implementation about how to get a new access token using refresh token in another StackOverflow answer
I am using basicAuth to authenticate POSTs on a specific address.
On the client side I am using a command of the form:
$.ajax({
type: "POST",
accepts: "text/plain",
url: "http://localhost:3000/somewhere",
data: JSON.stringify(something),
contentType: "application/json; charset=UTF-8",
dataType: "json",
success: function(data) {
window.alert("Received back: '" + data + "'");
},
username: theUsername,
password: "a password"
});
This is working fine, in the sense that the username stored in theUsername passes the authentication mechanism that I have on node. While the user is authenticated I can print a console.log statement and see who has actually authenticated (I am not validating the password at the moment). But then the actual processing starts for the POST request. However, at that point how can I figure out the username and the password used in the original request? I tried to look on the headers of the request but I don't see anything there.
When you receive a Basic authentication request you should be able to read the "authorization" header in req.headers.authorization You have to pull out the the base64 encoded credentials and then decode them. Presumably, in Express you use req.header("authorization") or req.get("authorization")
For a standalone example, take a look at https://gist.github.com/charlesdaniel/1686663 which I have copied underneath for future reference
var http = require('http');
var server = http.createServer(function(req, res) {
// console.log(req); // debug dump the request
// If they pass in a basic auth credential it'll be in a header called "Authorization" (note NodeJS lowercases the names of headers in its request object)
var auth = req.headers['authorization']; // auth is in base64(username:password) so we need to decode the base64
console.log("Authorization Header is: ", auth);
if(!auth) { // No Authorization header was passed in so it's the first time the browser hit us
// Sending a 401 will require authentication, we need to send the 'WWW-Authenticate' to tell them the sort of authentication to use
// Basic auth is quite literally the easiest and least secure, it simply gives back base64( username + ":" + password ) from the browser
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
res.end('<html><body>Need some creds son</body></html>');
}
else if(auth) { // The Authorization was passed in so now we validate it
var tmp = auth.split(' '); // Split on a space, the original auth looks like "Basic Y2hhcmxlczoxMjM0NQ==" and we need the 2nd part
var buf = new Buffer(tmp[1], 'base64'); // create a buffer and tell it the data coming in is base64
var plain_auth = buf.toString(); // read it back out as a string
console.log("Decoded Authorization ", plain_auth);
// At this point plain_auth = "username:password"
var creds = plain_auth.split(':'); // split on a ':'
var username = creds[0];
var password = creds[1];
if((username == 'hack') && (password == 'thegibson')) { // Is the username/password correct?
res.statusCode = 200; // OK
res.end('<html><body>Congratulations you just hax0rd teh Gibson!</body></html>');
}
else {
res.statusCode = 401; // Force them to retry authentication
res.setHeader('WWW-Authenticate', 'Basic realm="Secure Area"');
// res.statusCode = 403; // or alternatively just reject them altogether with a 403 Forbidden
res.end('<html><body>You shall not pass</body></html>');
}
}
});
server.listen(5000, function() { console.log("Server Listening on http://localhost:5000/"); });