I am working on a web application built using Spring framework. I am getting Invalid CSRF Token error. I see this behavior only in Chrome browser. Following are the steps followed:
Login to the application by providing userName and password
Click on Logout button to logout. The user will be re-directed to the login page
Then, in the login page again try to login. I am getting the below error
Invalid CSRF Token 'd82dfa89-81b1-449e-9ef5-cdd32957e7f3' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
Spring security configuration:
http.
addFilter(headerAdminFilter).
authorizeRequests().
regexMatchers("/login.*").permitAll().
regexMatchers("/api.*").fullyAuthenticated().
regexMatchers("/jolokia.*").hasRole(ADMINISTRATOR).
regexMatchers("/appadmin.*").hasRole(ADMINISTRATOR).
regexMatchers(".*").fullyAuthenticated().
and().
formLogin().loginPage("/login").successHandler(new RedirectingAuthenticationSuccessHandler()).
and().
exceptionHandling().authenticationEntryPoint(new RestAwareAuthenticationEntryPoint("/login"));
HTML code for Logout button:
<a id="logout-button" ng-click="ac.logout()" href="/login">Log Out</a>
AngularJS code for logout function:
this.logout = function () {
$http.post("/logout");
}
The following javascript snippet fixes stale CSRF token. The idea is to fetch a fresh token when the user tries to submit the login form and update the CSRF value in the form before the form is actually submitted.
$(document).ready(function() {
$("form[method=post]").submit(function(event) {
var form = this;
$.get("csrf-token", function(content) {
$(form).find("input[name=_csrf]").val(content);
form.submit();
});
event.preventDefault();
});
});
You need a /csrf-token mapping on the server side that returns the current CSRF token.
Related
I have a functioning logout button which successfully ends a session established using express server ExpressOIDC/express-session. The OIDC sessions and the user is redirected to the logged out view. The logout button html is shown here:
<form v-if="authenticated" method="POST" action="/logout" id="auth-logout-form" v-cloak>
<span class="user-name">{{userName}}</span>
<input type="submit" value="Logout" />
</form>
I simply want to mimic this function in my code for inactivity logout.
First I tried a simple $.post('logout'). But this produces the following errors:
Access to XMLHttpRequest at 'https://xxxxxxxx.oktapreview.com/oauth2/default/v1/logout?id_token_hint=xxxxxxxxx&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A3000%2F' (redirected from 'https://localhost:3000/logout') from origin 'https://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
jquery.min.js:2
GET https://xxxxxx.oktapreview.com/oauth2/default/v1/logout?id_token_hint=XXXXXXXXXXXXXXXX&post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A3000%2F net::ERR_FAILED
I then tried this ajax call:
if (this.currSeconds >= 5 && this.authenticated === true) {
// this.logoutOIDC
$.ajax({
url: 'logout',
method: "POST",
headers: {
'Access-Control-Allow-Origin': 'https://localhost:3000/',
},
type: 'dataType',
/* etc */
success: function(jsondata) {
console.log(jsondata)
},
})
}
This produces the same errors.
Of course, "$('#auth-logout-form').trigger('submit')" works great, but it does not seem like the proper approach, and I believe this leaves logout functionality too easily disabled.
The suggestion from Okta was to go ahead and use $('#auth-logout-form').trigger('submit') if I was going to use the middleware. There may be another way, but this method was suitable under the circumstances.
I am using Google API Client for Google Analytics with OAuth 2.0
I read this to get the refresh token but it doesn't appears: https://developers.google.com/identity/protocols/OAuth2WebServer#offline
I only get this instead:
{
"access_token": "ya29.twHFi4LsiF-AITwzCpupMmie-fljyTIzD9lG8y_OYUdEGKSDL7vD8LPKIqdzRwvoQAWd",
"token_type": "Bearer",
"expires_in": 3599,
"id_token": "very long string"
}
Here is the code:
Javascript (to get the Authorization Code): that works
gapi.analytics.ready(function() {
gapi.analytics.auth.authorize({
container: 'embed-api-auth-container',
clientid: '257497260674-ji56vq885ohe9cdr1j6u0bcpr6hu7rde.apps.googleusercontent.com',
});
gapi.analytics.auth.on('success', function(response) {
var code = response.code;
$.ajax({
url: "getTokensFromCode.php",
method: "GET",
data: {
"code": code
},
success: function (tokens) {
// I can't get refresh token here, only get "access_token" and "id_token"
console.log(tokens);
}
});
});
});
PHP (to exchange Authorization Code for tokens): that doesn't work
// I get the authorization code in Javascript
$code = $_GET['code'];
$redirectURI = "postmessage";
$client = new Google_Client();
$client->setClientId($clientID);
$client->setClientSecret($clientSecret);
$client->setRedirectUri($redirectURI);
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->authenticate($code);
$tokens = $client->getAccessToken();
echo $tokens;
I need the refresh token in Javascript, that's why I get the authorization code in Javascript and make an Ajax request to get the refresh token.
You will only get the refresh_token the very first time that a user grants access to your app. You'll need to store the refresh_token somewhere to be able to use it afterwards. You won't get a new refresh token the next time a user logs in to your app.
FWIW: using a refresh token in a Javascript client doesn't make a lot of sense since a Javascript client can't store it in a safe (confidential) way. Since Javascript clients live in browsers and users are present in browsers, you can just redirect the browser to the authorization endpoint again and you'll get a new access token (which is also the purpose of a refresh token). The SSO session for the user at Google will make sure that the user doesn't need to login again and that the experience is seamless.
Check out this response by #curious_coder
Google API Refresh Token
He explains a way to get the Refresh Token every time :) Life saver. This post helped me get there so I figured I'd share this for any one else trying to find their refresh token. Add the last two lines of this code to your Auth process
$client = new Google_Client();
$client->setAuthConfigFile(__DIR__ . '/client_secrets.json');
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/dashboard/oauthcallbacks');
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->setAccessType('offline'); //To fetch refresh token(Refresh token issued only first time)
$client->setApprovalPrompt('force'); //Adding this will force for refresh token
Also I used var_dump($tokens) to get the contents instead of echo. Dont remember if it makes a difference in PHP but $tokens will be an array of objects.
I am attempting to perform an AJAX submission to Laravel. According to Laravel's documentation here, I need to add a CSRF token as both a request header and POST parameter. I'm retrieving the CSRF Token from the XSRF-TOKEN cookie as also described in the documentation:
// Grab XSRF cookie.
var csrf_token;
var cookies = document.cookie.split(';');
for(cookie_offset in cookies) {
var cookie_parts = cookies[cookie_offset].split('=');
if(cookie_parts[0].trim() === 'XSRF-TOKEN') {
csrf_token = cookie_parts[1].trim();
}
}
This retrieves a token similar to the following:
eyJpdiI6Ik96QTdtcFIzam85TGJNQ3pzNUF1blE9PSIsInZhbHVlIjoiQm8zelBSaFpaM0JZcjlxcURFVEZNenZWMzNxUHFBMm1VVzM3YXpBbjVvaTBReEY5cFA1RGV3UVBHQWhjVGhZYmtDZ2lacGxFejJwQkxHaGplV1wvVEtRPT0iLCJtYWMiOiI3NTlkZmI5ODU2YTdlN2RiYTA1YTAyM2NiZmZlOWUwZTQyY2I0NTUzOWEyNzI5YjE2ODIyMmU1YzZiNDE1MmQ0In0
Once I have the token, I am adding it as a X-CSRF-Token header and _token POST parameter.
When I perform the request, I'm receiving this error:
TokenMismatchException in VerifyCsrfToken.php line 46
I've also tried passing the token as a X-XSRF-TOKEN header instead, but am getting the same error
When I set the X-XSRF-TOKEN and omit the _token POST parameter, I encounter a different error:
DecryptException in Encrypter.php line 142:
Invalid data.
What is it that I am missing here?
UPDATE:
After some more debugging and comparing this request with requests being generated by an AngularJS implementation which ran side-by-side this implementation, I discovered the problem was that I needed to URL Decode the cookie's value.
I simply needed to do the following, after extracting the csrf token from the cookies:
csrf_token = decodeURIComponent(csrf_token );
You add the token to the client's request either for AJAX or as a hidden field in a form. You have to add a way for the client to know what the token is. One method is to use the token meta tag from the base view:
<meta name="xsrf-token" content="{{Session::token()}}" />
Then in your javascript side for any ajax request you can do:
$(document).ready(function(){
$.ajaxSetup({
headers: {
'X-CSRF-Token':$('meta[name="xsrf-token"]).attr('content')
}
});
}
Otherwise when you post you can use the CSRF token in a hidden field
as so:
<input type="hidden" name="_token" value="{{Session::token()}}">
I am building a web app using spring security and thymeleaf, I got it work with login and logout, but I have some issues when I try to register as an end user. I am getting an invalid csrf token error. I am new to this, and I could use some help. My question is how can I attach a token to that request?(Post /registration) By the way I haven't used any XML, I use annotations.
This is the request
$scope.registerUser = function() {
$http.post(BASE_URL + "/registration", $scope.registrationRequest).
success(function (data, status, headers, config) {
$log.info("Success: " + headers('Location'));
$scope.hasRegistered = true;
}).error(function(data, status, headers, config) {
$scope.hasRegisterErrors = true;
$log.info("Error: status =" + status + ", body =" + JSON.stringify(data));
});
}
And the error
Error: status =403, body ={"timestamp":1430645356572,"status":403,"error":"Forbidden","message":"Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.","path":"/registration"}
The response message is telling you that the server expects your POST request to include either a "_csfr" parameter or an "X-CSRF-TOKEN" header. Presumably, your server has CSFR protection enabled. This is default behaviour with Spring Security though you can disable it. (I wouldn't recommend doing that.)
The Cross Site Request Forgery (CSRF) chapter of the Spring Security documentation explains what this is all about. This section explains how to implement CSRF tokens, including examples that how to embed the token in an HTML <form> or include it in an AJAX request.
you should add csrf parameter as a parameter in your request. for example, use following code to define an input in your form to get csrf token and add as request parameter for sending post request by form:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
or use following code to send as ajax call:
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
Then construct the header:
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
so, you need get srcf token to send as a parameter in your request. by spring security you can define csrf parameter name by following configuration:
<http>
<!-- ... -->
<csrf token-repository-ref="tokenRepository"/>
</http>
<bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
p:cookieHttpOnly="false">
<property name="sessionAttributeName" value="_csrf"/>
<property name="headerName" value="_csrf_header"/>
</bean>
for more, see following links:
https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html
https://spring.io/blog/2013/08/21/spring-security-3-2-0-rc1-highlights-csrf-protection/
http://www.baeldung.com/spring-security-csrf
I am getting status code 403 when I try to log in after successfully being logged in and logged out.
Client side is written in Angular and server side is in Django.
This goes as follows:
Client requests url '/' fetches main HTML template with all required static files ( angular, bootstrap, jQuery sources and angular sources defined by me) with
<div ng-view></div> tag into which further templates will be inserted.
Via $location service is redirected to url '/#/login'
This rule from $routeProvider is executed once '/#/login' is hit:
$routeProvider.when('/login', {
templateUrl: 'login.html'
});
'login.html' is served by django view and form for logging in is rendered to the user
User logs in successfully providing proper credentials
Then user logs out, by clicking on a button, which fires '$http.get(
'/logout/'
);' and then is redirected to url '/#/login'
Here is the problem. When user fills in credential form and sends 'POST' request, 403 is returned. I thought that it is, because this routing is done only by angular and since 'login.html' template has already been requested it has been catched and can be served without hitting backend, but after logging out currently possesed CSRF cookie is stale, so that's why I am getting 403. So I tried to remove that template:
logout: function(){
var forceLoginTemplateRequest = function(){
if( $templateCache.get('login.html') !== 'undefined'){
$templateCache.remove('login.html');
}
};
var responsePromise = $http.get(
urls.logout
);
responsePromise.success(forceLoginTemplateRequest);
return responsePromise;
}
After doing that I could see client side requesting 'login.html' template always after logging out, so I thought I could provide CSRF cookie when serving that template from backend:
#urls.py
urlpatterns = patterns(
'',
...
url(r'^$', serve_home_template),
url(r'^login.html$', serve_login_template),
url(r'^login/', login_view, name='login'),
url(r'^logout/', logout_view, name='logout'),
...
)
#views.py
#ensure_csrf_cookie
def serve_login_template(request):
return render(request, "login.html")
#ensure_csrf_cookie
def serve_home_template(request):
return render(request, 'home.html')
But still it doesn't work and I am getting 403 when trying to log in after logging out. The only way I managed it to work is to simply refresh the page so that every single file, whether template or source file is requested again from the backend and CSRF cookie is updated with them.
Here is my app's run section for making sure CSRF cookie is sent with every request:
mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService', '$templateCache',
function($http, $cookies, $location, $rootScope, AuthService, $templateCache) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
if ( !(AuthService.isLoggedIn() == "true")){
$location.path('/login');
}
});
}]);
This could be a cache problem. Try to add the never_cache decorator to all your views:
from django.views.decorators.cache import never_cache
...
#ensure_csrf_cookie
#never_cache
def serve_login_template(request):
return render(request, "login.html")
...
I solved this problem by setting X-CSRFTOKEN header in $routeChangeStart event.
I don't exactly know how module.run phase works, but it seems that when certain event defined within it occurs everything what is defined outside this event's handler body isn't executed.
mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService',
function($http, $cookies, $location, $rootScope, AuthService) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
// Added this line
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
if ( !(AuthService.isLoggedIn() == "true")){
$location.path('/login');
}
});
}]);
This works together with removing 'login.html' template from $templateCache.
Instead of removing templates on client side with $templateCache service it is also possible to set your server to serve templates and set following headers:
Cache-Control: no-cache, no-store, must-revalidate
Pragma : no-cache
Expires : 0
Another way of dealing with this problem is to simply force page refresh, however I don't like this approach, since this is not pro-single-page-app approach. :)
One solution could be to read the current, fresh csrftoken directly from the cookie and then update the stale cookie using javascript.
var fresh_token = document.cookie.match('csrftoken=([a-zA-Z0-9]{32})