Laravel: FormRequest validation failing when I add required rule - javascript

I have a FormRequest class set up to validate input from my frontend (in the shape of the FormData object) however it's acting really weird with my fields (title and body).
Despite FormData being sent (I check network tab and do $request->all()) I'm getting the title and body fields are required 244 validation error,
I also noticed that after removing the required rule the validation PASSES SUCCESSFULLY even if my both my inputs are less than 5 characters (this shouldn't happen). Any idea what could be causing this?
So right now if required rule my input passes and is added to the database if I add it back validation fails and field required message pops up.
My FormRequest:
<?php
namespace App\Http\Requests\bulletins;
use Illuminate\Foundation\Http\FormRequest;
class CreateBulletin extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'image' => 'nullable|sometimes|image|mimes:jpeg,bmp,png,jpg,svg|max:2000',
'title' => 'string|min:5|max:250',
'body' => 'string|min:5|max:250',
];
}
}
My controller method:
public function store(CreateBulletin $request)
{
//dd($request->all());
$bulletin = new Bulletin();
$bulletin->title = $request->input('title');
$bulletin->body = $request->input('body');
$bulletin->image = '/img/others/default.jpg';
if($request->hasFile('image')){
$uploadFile = $request->file('image');
$filename = str_random(6).'.'.$uploadFile->extension();
$uploadFile->storeAs('uploads', $filename);
$bulletin->image = '/uploads/'.$filename;
}
$bulletin->save();
return response()->json([
'bulletin' => $bulletin,
]);
}
The shape of my data being sent:
Checking parameters sent at network tab:
-----------------------------1607382142848
Content-Disposition: form-data; name="title"
title of bulletin
-----------------------------1607382142848
Content-Disposition: form-data; name="body"
content of bulletin
-----------------------------1607382142848--
OR
after doing dd($request->all())
array:3 [
"title" => "title of bulletin"
"body" => "content of bulletin"
"image" => UploadedFile {#971
-test: false
-originalName: "01-uxpin-modal-signup-form.jpg"
-mimeType: "image/jpeg"
-error: 0
#hashName: null
path: "C:\xampp\tmp"
filename: "php7708.tmp"
basename: "php7708.tmp"
pathname: "C:\xampp\tmp\php7708.tmp"
extension: "tmp"
realPath: "C:\xampp\tmp\php7708.tmp"
aTime: 2018-12-04 11:45:56
mTime: 2018-12-04 11:45:56
cTime: 2018-12-04 11:45:56
inode: 0
size: 48989
perms: 0100666
owner: 0
group: 0
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
linkTarget: "C:\xampp\tmp\php7708.tmp"
}
]
So as you can see my data hits the server

Try something like this and see if you get same error:
<?php
namespace App\Http\Requests\bulletins;
use App\Http\Requests\Request;
class CreateBulletin extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'image' => 'nullable|sometimes|image|mimes:jpeg,bmp,png,jpg,svg|max:2000',
'title' => 'string|min:5|max:250',
'body' => 'string|min:5|max:250',
];
}
}
Need to remove FormRequest from class to remove that error you got after replacing Request.
Hope this helps.

Before set the values you have to test the request using:
$validated = $request->validated();
then(for example the title field which has a rule in the form request):
$bulletin->title = $validated->input('title');
Make sure of the imports in the controller also:
use Illuminate\Http\Request; for the request;
and
use App\Http\Requests\CreateBulletin;

Related

IE 11 retrieving old version of the object with web sockets via Stompjs within ReactJS with Spring Boot/Spring Data REST

I originally thought I could not get web sockets working under IE 11. Now after adding a whole lot of debugging details I have noticed that the sockets are being called correctly.
The problem is that somehow IE is returning old data. I notice that after updating an object in the application with IE 11 that whenever it goes to retrieve that object again it will not get the latest copy. The ETag in the HTTP Header is set to the original version of the object not the updated one.
Here is my example:
1) Go to the main application page http://localhost:8081
This page loads the user page by default. This page will use ReactJS to load the User objects from my Spring application.
2) Update the user's description, from UpdateNow to changed1.
3) This trigges an update and the websocket to update the user object in the database.
Problem
When I reload the page in IE it shows the old data. Even by closing the browser and opening a new one it continues to show the old data. When enabling IE debugging I can see the ETag in the header is set to the original not the updated version.
componentDidMount() {
log("calling componentDidMount");
this.loadFromServer(this.state.pageSize);
In IE the following is returned:
loadFromServer for type users
Opening Web Socket...
loadFromServer::: objectCollection:
[object Object]{entity: Object {...}, headers: Object {...}, raw: XMLHttpRequest {...}.....
loadFromServer::: objects:
Here is where the old data is loaded inside the objects array for the first user:
firstName: Aaron
lastName: Smith
description: UpdateNow (this is old in the db it is currently set to changed1)
If we do the same in Chrome the above steps are the same but we get the current User object
description: "changed1"
firstName : "Aaron"
lastName : "Smith"
I can further tell that my IE data is incorrect because when I try to make a change to that user I will get an 412 precondition failed status code when I go to do a HTTP PUT for a update for this user telling me the copy is stale.
Problem 2
I can update the object in Chrome for a User. When I do this I can see IE update its version of the objects in the console but it continues to retrieve the older version and not the new version of the object.
Question
Why is IE getting this old data? It looks like it is somehow cached within it. I have turned off history in the browser, I have deleted everything and even closed and reopened the browser but it continually retrieves the old data. Even when I do CTRL-F5 it will keep the old data.
Another odd thing I notice is that I can not see the Network Body Request/Response from within IE using the console debug. It comes up as This resource has no response payload data even though I can see the headers get a HTTP 200 when retrieving the user at http://localhost:8081/api/users/1. I can view the headers and they show me that the ETag is out of date for IE using the original ETag and not the new one.
If I view the request and response in another client it looks fine. I'm wanting to know how/why IE is somehow continuing to access the old data.
Thank you
Code/Setup:
app.js
const stompClient = require('./websocket-listener');
..... (other functions)
loadFromServer(pageSize, newObjectType) {
console.log("loadFromServer for type " + this.state.objectType);
var currentObjectType = this.state.objectType;
// check to see if we are refreshing the page for a new object type
if (newObjectType != null) {
log("setting new objectType of " + newObjectType);
// set the new object type for the page and refresh the results with that tyupe
currentObjectType = newObjectType;
}
follow(client, root, [
{rel: currentObjectType, params: {size: pageSize}}]
).then(objectCollection => {
console.log("loadFromServer::: objectCollection:")
console.log(objectCollection)
return client({
method: 'GET',
path: objectCollection.entity._links.profile.href,
headers: {'Accept': 'application/schema+json'}
}).then(schema => {
this.schema = schema.entity;
this.links = objectCollection.entity._links;
log("loadFromServer::: schema:")
log(schema)
return objectCollection;
});
}).then(objectCollection => {
this.page = objectCollection.entity.page;
log("loadFromServer::: objectCollection 2:")
log(objectCollection)
return this.getObjectMap(currentObjectType, objectCollection);
}).then(objectPromises => {
log("loadFromServer::: objectPromises:")
log(objectPromises)
return when.all(objectPromises);
}).done(objects => {
console.log("loadFromServer::: objects:")
console.log(objects)
this.setState({
page: this.page,
objects: objects,
attributes: Object.keys(this.schema.properties),
pageSize: pageSize,
links: this.links,
objectType: currentObjectType
});
});
}
componentDidMount() {
log("calling componentDidMount");
this.loadFromServer(this.state.pageSize);
// TODO add web socket callbacks for all entity types
stompClient.register([
{route: '/topic/newUser', callback: this.refreshAndGoToLastPage},
{route: '/topic/updateUser', callback: this.refreshCurrentPage},
{route: '/topic/deleteUser', callback: this.refreshCurrentPage},
{route: '/topic/newTestCase', callback: this.refreshAndGoToLastPage},
{route: '/topic/updateTestCase', callback: this.refreshCurrentPage},
{route: '/topic/deleteTestCase', callback: this.refreshCurrentPage},
{route: '/topic/newTestSuite', callback: this.refreshAndGoToLastPage},
{route: '/topic/updateTestSuite', callback: this.refreshCurrentPage},
{route: '/topic/deleteTestSuite', callback: this.refreshCurrentPage}
]);
}
client.js
'use strict';
var rest = require('rest');
var defaultRequest = require('rest/interceptor/defaultRequest');
var mime = require('rest/interceptor/mime');
var uriTemplateInterceptor = require('./api/uriTemplateInterceptor');
var errorCode = require('rest/interceptor/errorCode');
var baseRegistry = require('rest/mime/registry');
var registry = baseRegistry.child();
registry.register('text/uri-list', require('./api/uriListConverter'));
registry.register('application/hal+json', require('rest/mime/type/application/hal'));
module.exports = rest
.wrap(mime, { registry: registry })
.wrap(uriTemplateInterceptor)
.wrap(errorCode)
.wrap(defaultRequest, { headers: { 'Accept': 'application/hal+json' }});
Web socket listner.js
'use strict';
var SockJS = require('sockjs-client'); // <1>
require('stompjs'); // <2>
function register(registrations) {
var socket = SockJS('/mcbserver'); // <3>
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
registrations.forEach(function (registration) { // <4>
stompClient.subscribe(registration.route, registration.callback);
});
});
}
module.exports.register = register;
package.json
"dependencies": {
"babel-core": "^6.8.0",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-2": "^6.5.0",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"rest": "^1.3.2",
"sockjs-client": "^1.0.3",
"stompjs": "^2.3.3",
"webpack": "^1.13.0",
"when": "^3.7.7",
"react-bootstrap": "^0.29.3",
"jquery": "^2.2.3"
},
WebSocketConfiguration.java
`
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
#Component
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
static final String MESSAGE_PREFIX = "/topic";
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/mcbserver").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker(MESSAGE_PREFIX);
registry.setApplicationDestinationPrefixes("/app");
}
}
EventHandler.java
#Component
#RepositoryEventHandler(User.class)
public class EventHandler {
private final SimpMessagingTemplate websocket;
private final EntityLinks entityLinks;
#Autowired
public EventHandler(SimpMessagingTemplate websocket, EntityLinks entityLinks) {
this.websocket = websocket;
this.entityLinks = entityLinks;
}
#HandleAfterCreate
public void newUser(User user) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/newUser", getPath(user));
}
#HandleAfterDelete
public void deleteUser(User user) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/deleteUser", getPath(user));
}
#HandleAfterSave
public void updateUser(User user) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/updateUser", getPath(user));
}
#HandleAfterCreate
public void newTestCase(TestCase testCase) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/newTestCase", getPath(testCase));
}
#HandleAfterDelete
public void deleteTestCase(TestCase testCase) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/deleteTestCase", getPath(testCase));
}
#HandleAfterSave
public void updateTestCase(TestCase testCase) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/updateTestCase", getPath(testCase));
}
#HandleAfterCreate
public void newTestSuite(TestSuite testSuite) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/newTestSuite", getPath(testSuite));
}
#HandleAfterDelete
public void deleteTestSuite(TestSuite testSuite) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/deleteTestSuite", getPath(testSuite));
}
#HandleAfterSave
public void updateTestSuite(TestSuite testSuite) {
this.websocket.convertAndSend(MESSAGE_PREFIX + "/updateTestSuite", getPath(testSuite));
}
/**
* Take an {#link User} and get the URI using Spring Data REST's
* {#link EntityLinks}.
*
* #param user
*/
private String getPath(User user) {
return this.entityLinks.linkForSingleResource(user.getClass(), user.getId()).toUri().getPath();
}
/**
* Take an {#link TestCase} and get the URI using Spring Data REST's
* {#link EntityLinks}.
*
* #param user
*/
private String getPath(TestCase testCase) {
return this.entityLinks.linkForSingleResource(testCase.getClass(), testCase.getId()).toUri().getPath();
}
/**
* Take an {#link TestSuite} and get the URI using Spring Data REST's
* {#link EntityLinks}.
*
* #param user
*/
private String getPath(TestSuite testSuite) {
return this.entityLinks.linkForSingleResource(testSuite.getClass(), testSuite.getId()).toUri().getPath();
}
}
After doing additional debugging with F12 in IE 11 I could actually see that IE was caching the details for each of the HTTP GET Request. I then found this guide:
https://support.microsoft.com/en-ca/kb/234067
After updating my Header Requests with the following it worked fine.
'Pragma': 'no-cache', 'Expires': '-1', 'cache-control': 'no-cache'

Disable Csrf for ajax query

I'm using Laravel 5.1 and i'm tryin to disable csrf validation for this route to be able to perform some remote validations using Jquery Form Validator :
Route::post('verify', 'formController#check');
As mentioned in the documentation, I just have to add my URI to the $excludeproperty. whice I did :
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* #var array
*/
protected $except = [
'verify',
];
}
That did not work, So I tried to disable csrf validation for the whole application :
class Kernel extends HttpKernel
{
protected $middleware = [
...
//\App\Http\Middleware\VerifyCsrfToken::class,
];
protected $routeMiddleware = [
...
];
}
That did not work either. I keep getting this error on the console :
POST http://domain.name/verify 500 (Internal Server Error)
whice exactly points to this line(The validator's js file):
ajax({url:b,type:"POST",cache:!1,data:g,dataType:"json",error:function(a){return h({valid:!1,message:"Connection failed with status: "+a.statusText},f),!1}
What am I missing? thanks for your help.
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
protected $except_urls = [
'verify'
];
public function handle($request, Closure $next)
{
$regex = '#' . implode('|', $this->except_urls) . '#';
if ($this->isReading($request) || $this->tokensMatch($request) || preg_match($regex, $request->path()))
{
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}
}
Solved the problem.
For Laravel 5 and above, adding protected $except = ['verify',]; to App\Http\Middleware\VerifyCsrfToken.php does solve the problem.
NB : Google's inspect tool (Network menu) helped me understand what was really happening.

Laravel 5 Validation - Return as json / ajax

I am trying to post the values into validation and return the response as json rather than return view as given in the documentation.
$validator = Validator::make($request->all(), [
'about' => 'min:1'
]);
if ($validator->fails()) {
return response()->json(['errors' => ?, 'status' => 400], 200);
}
The post is made by ajax so I need to receive the response in the ajax as well.
I figured out that in order to prevent refresh of the page in the returning response, I have to give it a status code of 200 outside the array. But I couldn't figure out what to give the 'errors' part. What should I write in there?
You can use $validator->messages() that returns an array which contains all the information about the validator, including errors. The json function takes the array and encodes it as a json string.
if ($validator->fails()) {
return response()->json($validator->messages(), Response::HTTP_BAD_REQUEST);
}
Note: In case of validation errors, It's better not to return response code 200. You can use other status codes like 400 or Response::HTTP_BAD_REQUEST
You can also tell Laravel you want a JSON response. Add this header to your request:
'Accept: application/json'
And Laravel will return a JSON response.
In Laravel 5.4 the validate() method can automatically detect if your request is an AJAX request, and send the validator response accordingly.
See the documentation here
If validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an AJAX request, a HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors.
So you could simply do the following:
Validator::make($request->all(), [
'about' => 'min:1'
])->validate();
I use below this code for API in my existing project.
$validator = Validator::make($request->all(), [
'ride_id' => 'required',
'rider_rating' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}
in case you are using a request class.
you may use failedValidation to handle you failed
/**
* Returns validations errors.
*
* #param Validator $validator
* #throws HttpResponseException
*/
protected function failedValidation(Validator $validator)
{
if ($this->wantsJson() || $this->ajax()) {
throw new HttpResponseException(response()->json($validator->errors(), 422));
}
parent::failedValidation($validator);
}
For those who have created a custom request class can override the public function response(array $errors) method and return a modified response without Validator explicitly.
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\JsonResponse;
class CustomRequest extends FormRequest
{
public function rules()
{
$rules = [
//custom rules
];
return $rules;
}
public function response(array $errors)
{
return new JsonResponse(['error' => $errors], 400);
}
}
My solution is to make my own FormRequest class which I put in the root API namespace namespace App\Http\Requests\Api.
Hope this helps someone
https://jamesmills.co.uk/2019/06/05/how-to-return-json-from-laravel-form-request-validation-errors/
Actually I used #Soura solution but with a little change. You need to import the Validator package as well.
$validator = \Validator::make($request->all(), [
'ride_id' => 'required',
'rider_rating' => 'required',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 400);
}

FOSUserBundle AJAX Login with Symfony2 (routing)

I'm trying to make the AJAX authentication work with FOSUserBundle.
I have created an Handler directory with a AuthenticationHandler class :
<?php
namespace BirdOffice\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
private $router;
private $session;
/**
* Constructor
*
* #param RouterInterface $router
* #param Session $session
*/
public function __construct( RouterInterface $router, Session $session )
{
$this->router = $router;
$this->session = $session;
}
/**
* onAuthenticationSuccess
*
* #param Request $request
* #param TokenInterface $token
* #return Response
*/
public function onAuthenticationSuccess( Request $request, TokenInterface $token )
{
// if AJAX login
if ( $request->isXmlHttpRequest() ) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
// if form login
} else {
if ( $this->session->get('_security.main.target_path' ) ) {
$url = $this->session->get( '_security.main.target_path' );
} else {
$url = $this->router->generate( 'home_page' );
} // end if
return new RedirectResponse( $url );
}
}
/**
* onAuthenticationFailure
*
* #param Request $request
* #param AuthenticationException $exception
* #return Response
*/
public function onAuthenticationFailure( Request $request, AuthenticationException $exception )
{
// if AJAX login
if ( $request->isXmlHttpRequest() ) {
$array = array( 'success' => false, 'message' => $exception->getMessage() ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
// if form login
} else {
// set authentication exception to session
$request->getSession()->set(SecurityContextInterface::AUTHENTICATION_ERROR, $exception);
return new RedirectResponse( $this->router->generate( 'login_route' ) );
}
}
}
I have created a login Javascript function :
function login() {
$.ajax({
type: "POST",
url: Routing.generate('check_login_ajax'),
dataType: 'json',
data: {
_username: $('#username').val(),
_password: $('#password').val(),
_remember_me: false,
_csrf_token: $('#_csrf_token').val()
}
}).done(function(data) {
console.log(data);
}).fail(function(data) {
console.log(data);
});
}
In my routingAjax.yml, I have added the following lines to override the FOSUserBundle security route :
check_login_ajax:
pattern: /check_login_ajax
defaults: { _controller: FOSUserBundle:Security:check }
requirements:
_method: POST
options:
expose: true
In my global security.yml file, I have added the check_path, success_handler and failure_handler parts :
firewalls:
main:
pattern: ^/
form_login:
login_path: fos_user_registration_register
check_path: check_login_ajax
success_handler: user.security.authentication_handler
failure_handler: user.security.authentication_handler
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout:
path: fos_user_security_logout
target: /
anonymous: true
My first issue is : the AJAX return this message: "Invalid CSRF token." (but I send a good one generated in PHP, maybe I missed something doing it). Here is my PHP code for it :
<?php
$csrfProvider = $this->container->get('form.csrf_provider');
$csrfToken = $csrfProvider->generateCsrfToken('popUpUser');
?>
Second issue : my login page (not the AJAX one) is not working anymore because the orignal route of FOSUserBundle login has been overwritten.
PS : I have posted a message yesterday : FOSUserBundle (login / register) + AJAX + Symfony2 but I have badly explained my problem. Sorry by advance.
First Issue: You are sending an invalid CSRF token. In Symfony 2.3 you could generate it using {{ csrf_token('authenticate') }} inside the template's input's value.
Second issue: Do not overwrite the route, simply use the original route: fos_user_security_check.
In general: if you use an AuthenticationSuccessHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler your method could look something like this:
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
return new JsonResponse(array('success' => true));
}
return parent::onAuthenticationSuccess($request, $token);
}
Do something similar for an AuthenticationFailureHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler.

Where exactly to put the antiforgeryToken

I have a layout page that has a form with AntiForgeryToken
using (Html.BeginForm(action, "Account", new { ReturnUrl = returnUrl }, FormMethod.Post, new { Id = "xcrf-form" }))
This generates a hidden field
<input name="__RequestVerificationToken" type="hidden" value="p43bTJU6xjctQ-ETI7T0e_0lJX4UsbTz_IUjQjWddsu29Nx_UE5rcdOONiDhFcdjan88ngBe5_ZQbHTBieB2vVXgNJGNmfQpOm5ATPbifYE1">
In my angular view (that is loaded in a div in the layout page, I do this
<form class="form" role="form" ng-submit="postReview()">
And my code for postReview() is as follows
$scope.postReview = function () {
var token = $('[name=__RequestVerificationToken]').val();
var config = {
headers: {
"Content-Type": "multipart/form-data",
// the following when uncommented does not work either
//'RequestVerificationToken' : token
//"X-XSRF-TOKEN" : token
}
}
// tried the following, since my other MVC controllers (non-angular) send the token as part of form data, this did not work though
$scope.reviewModel.__RequestVerificationToken = token;
// the following was mentioned in some link I found, this does not work either
$http.defaults.headers.common['__RequestVerificationToken'] = token;
$http.post('/Review/Create', $scope.reviewModel, config)
.then(function (result) {
// Success
alert(result.data);
}, function (error) {
// Failure
alert("Failed");
});
}
My MVC Create method is as follows
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Create([Bind(Include = "Id,CommentText,Vote")] ReviewModel reviewModel)
{
if (User.Identity.IsAuthenticated == false)
{
// I am doing this instead of [Authorize] because I dont want 302, which browser handles and I cant do client re-direction
return new HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
// just for experimenting I have not yet added it to db, and simply returning
return new JsonResult {Data = reviewModel, JsonRequestBehavior = JsonRequestBehavior.AllowGet};
}
So no matter where I put the token, no matter what I use for 'Content-Type' (I tried application-json and www-form-urlencoded) I always get the error "The required anti-forgery form field "__RequestVerificationToken" is not present."
I even tried naming __RequestVerificationToken and RequestVerificationToken
Why does my server not find the damn token?
I also looked at couple of links that ask you to implement your own AntiForgeryToeknVerifyAttrbute and verify the token that is sent as cookieToken:formToken, I have not tried that but why I am not able to get it working whereas this works for the MVC controllers (non-angular posts)
Yes. By default, MVC Framework will check for Request.Form["__RequestVerificationToken"].
Checking the MVC source code
public AntiForgeryToken GetFormToken(HttpContextBase httpContext)
{
string value = httpContext.Request.Form[_config.FormFieldName];
if (String.IsNullOrEmpty(value))
{
// did not exist
return null;
}
return _serializer.Deserialize(value);
}
You need to create your own filter to check it from Request.Header
Code Snippet from Phil Haack's Article - MVC 3
private class JsonAntiForgeryHttpContextWrapper : HttpContextWrapper {
readonly HttpRequestBase _request;
public JsonAntiForgeryHttpContextWrapper(HttpContext httpContext)
: base(httpContext) {
_request = new JsonAntiForgeryHttpRequestWrapper(httpContext.Request);
}
public override HttpRequestBase Request {
get {
return _request;
}
}
}
private class JsonAntiForgeryHttpRequestWrapper : HttpRequestWrapper {
readonly NameValueCollection _form;
public JsonAntiForgeryHttpRequestWrapper(HttpRequest request)
: base(request) {
_form = new NameValueCollection(request.Form);
if (request.Headers["__RequestVerificationToken"] != null) {
_form["__RequestVerificationToken"]
= request.Headers["__RequestVerificationToken"];
}
}
public override NameValueCollection Form {
get {
return _form;
}
}
}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute :
FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
var httpContext = new JsonAntiForgeryHttpContextWrapper(HttpContext.Current);
AntiForgery.Validate(httpContext, Salt ?? string.Empty);
}
public string Salt {
get;
set;
}
// The private context classes go here
}
Check out here for MVC 4 implementation, to avoid salt issue
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = false, Inherited = true)]
public sealed class ValidateJsonAntiForgeryTokenAttribute
: FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var httpContext = filterContext.HttpContext;
var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null,
httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
I had the same problem. Turned out that I don't need to set antiforgery token anywhere explicitly in my angular js code. The MVC controller expects this token value to be delivered from 1. the form field, 2. cookie. The filter equates and is happy when they match.
When we submit the form, hidden field for the anti forgery token automatically supplies its value. Cookie is automatically set by the browser. So as I said, we don't need to do anything explicitly.
The problem really is request's content-type. By default it goes as as application/json and therefore the a.f. token value (or rather any form data) is not received.
Following worked for me:
// create the controller
var RegisterController = function ($scope, $http) {
$scope.onSubmit = function (e) {
// suppress default form submission
e.preventDefault();
var form = $("#registerform");
if (form.valid()) {
var url = form.attr('action');
var data = form.serialize();
var config = {
headers: {
'Content-type':'application/x-www-form-urlencoded',
}
};
$http.post(url, data, config).success(function (data) {
alert(data);
}).error(function(reason) {
alert(reason);
});
}
};
};
As Murali suggested I guess I need to put the toekn in the form itself, so I tried putting the token as part of form data and I needed to encode the form data as explained in https://stackoverflow.com/a/14868725/2475810
This approach does not require any additional code on server side, also we do not need to create and join cookie and form token. Just by form-encoding the data and including token as one of the fields as explained in the answer above we can get it rolling.
You should perform the HTTP request in this way:
$http({
url: '/Review/Create',
data: "__RequestVerificationToken=" + token + "&param1=1&param2=2",
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
}).success(function(result) {
alert(result.data);
}).error(function(error) {
alert("Failed");
});

Categories

Resources