Add a sample demonstrating Antiforgery with AJAX
This commit is contained in:
parent
bf6406bc2a
commit
1c0996c625
|
|
@ -1,66 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Antiforgery;
|
|
||||||
using Microsoft.AspNet.Builder;
|
|
||||||
using Microsoft.AspNet.Http;
|
|
||||||
using Microsoft.Extensions.OptionsModel;
|
|
||||||
|
|
||||||
namespace AntiforgerySample
|
|
||||||
{
|
|
||||||
public class FormPostSampleMiddleware
|
|
||||||
{
|
|
||||||
private readonly IAntiforgery _antiforgery;
|
|
||||||
private readonly AntiforgeryOptions _options;
|
|
||||||
private readonly RequestDelegate _next;
|
|
||||||
|
|
||||||
public FormPostSampleMiddleware(
|
|
||||||
RequestDelegate next,
|
|
||||||
IAntiforgery antiforgery,
|
|
||||||
IOptions<AntiforgeryOptions> options)
|
|
||||||
{
|
|
||||||
_next = next;
|
|
||||||
_antiforgery = antiforgery;
|
|
||||||
_options = options.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Invoke(HttpContext context)
|
|
||||||
{
|
|
||||||
if (context.Request.Method == "GET")
|
|
||||||
{
|
|
||||||
var page =
|
|
||||||
@"<html>
|
|
||||||
<body>
|
|
||||||
<form action=""/"" method=""post"">
|
|
||||||
<input type=""text"" name=""{0}"" value=""{1}""/>
|
|
||||||
<input type=""submit"" />
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>";
|
|
||||||
|
|
||||||
var tokenSet = _antiforgery.GetAndStoreTokens(context);
|
|
||||||
await context.Response.WriteAsync(string.Format(page, _options.FormFieldName, tokenSet.RequestToken));
|
|
||||||
}
|
|
||||||
else if (context.Request.Method == "POST")
|
|
||||||
{
|
|
||||||
// This will throw if invalid.
|
|
||||||
await _antiforgery.ValidateRequestAsync(context);
|
|
||||||
|
|
||||||
var page =
|
|
||||||
@"<html>
|
|
||||||
<body>
|
|
||||||
<h1>Everything is fine</h1>
|
|
||||||
<h2><a href=""/"">Try Again</a></h2>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>";
|
|
||||||
await context.Response.WriteAsync(page);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _next(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNet.Antiforgery;
|
||||||
using Microsoft.AspNet.Builder;
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.OptionsModel;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace AntiforgerySample
|
namespace AntiforgerySample
|
||||||
{
|
{
|
||||||
|
|
@ -10,13 +17,57 @@ namespace AntiforgerySample
|
||||||
{
|
{
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAntiforgery();
|
services.AddRouting();
|
||||||
|
|
||||||
|
// Angular's default header name for sending the XSRF token.
|
||||||
|
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
|
||||||
|
|
||||||
|
services.AddSingleton<TodoRepository>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery, IOptions<AntiforgeryOptions> options, TodoRepository repository)
|
||||||
{
|
{
|
||||||
|
app.Use(next => context =>
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(context.Request.Path.Value, "/index.html", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// We can send the request token as a JavaScript-readable cookie, and Angular will use it by default.
|
||||||
|
var tokens = antiforgery.GetAndStoreTokens(context);
|
||||||
|
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseDefaultFiles();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseMiddleware<FormPostSampleMiddleware>();
|
|
||||||
|
var routes = new RouteBuilder(app);
|
||||||
|
|
||||||
|
routes.MapGet("api/items", (HttpContext context) =>
|
||||||
|
{
|
||||||
|
var items = repository.GetItems();
|
||||||
|
return context.Response.WriteAsync(JsonConvert.SerializeObject(items));
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.MapPost("api/items", async (HttpContext context) =>
|
||||||
|
{
|
||||||
|
// This will throw if the token is invalid.
|
||||||
|
await antiforgery.ValidateRequestAsync(context);
|
||||||
|
|
||||||
|
var serializer = new JsonSerializer();
|
||||||
|
using (var reader = new JsonTextReader(new StreamReader(context.Request.Body)))
|
||||||
|
{
|
||||||
|
var item = serializer.Deserialize<TodoItem>(reader);
|
||||||
|
repository.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.StatusCode = 204;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseRouter(routes.Build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace AntiforgerySample
|
||||||
|
{
|
||||||
|
public class TodoItem
|
||||||
|
{
|
||||||
|
[JsonProperty(PropertyName = "name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace AntiforgerySample
|
||||||
|
{
|
||||||
|
public class TodoRepository
|
||||||
|
{
|
||||||
|
private List<TodoItem> _items;
|
||||||
|
|
||||||
|
public TodoRepository()
|
||||||
|
{
|
||||||
|
_items = new List<TodoItem>()
|
||||||
|
{
|
||||||
|
new TodoItem() { Name = "Mow the lawn" },
|
||||||
|
new TodoItem() { Name = "Do the dishes" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<TodoItem> GetItems()
|
||||||
|
{
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(TodoItem item)
|
||||||
|
{
|
||||||
|
_items.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "ASP.NET",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"angular": "~1.4.8",
|
||||||
|
"bootstrap-css": "~3.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
/// <binding Clean='clean' />
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var gulp = require("gulp"),
|
||||||
|
bowerFiles = require('main-bower-files');
|
||||||
|
|
||||||
|
var paths = {
|
||||||
|
webroot: "./wwwroot/"
|
||||||
|
};
|
||||||
|
|
||||||
|
paths.bowerFilesDest = paths.webroot + '/bower_components';
|
||||||
|
|
||||||
|
gulp.task("copy:bower", function () {
|
||||||
|
return gulp.src(bowerFiles()).pipe(gulp.dest(paths.bowerFilesDest));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task("default", ["copy:bower"]);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "ASP.NET",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^3.9.0",
|
||||||
|
"gulp-concat": "^2.6.0",
|
||||||
|
"gulp-cssmin": "^0.1.7",
|
||||||
|
"gulp-uglify": "^1.5.1",
|
||||||
|
"main-bower-files": "^2.9.0",
|
||||||
|
"rimraf": "^2.4.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
{
|
{
|
||||||
"webroot": "wwwroot",
|
"webroot": "wwwroot",
|
||||||
"version": "1.0.0-*",
|
"version": "1.0.0-*",
|
||||||
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
|
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
"Microsoft.AspNet.Http.Abstractions": "1.0.0-rc2-16062",
|
||||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*",
|
"Microsoft.AspNet.Routing.Extensions": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*",
|
||||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||||
},
|
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
|
||||||
|
"Newtonsoft.Json": "7.0.1"
|
||||||
|
},
|
||||||
|
|
||||||
"commands": {
|
"commands": {
|
||||||
"kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000",
|
"kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000",
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,47 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Antiforgery Sample</title>
|
<title>Todo List Antiforgery Sample</title>
|
||||||
|
<link rel="stylesheet" href="bower_components/bootstrap.min.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body ng-app="TODO" ng-controller="todoController">
|
||||||
<h1>Hello, World!</h1>
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<h1>Todo List Antiforgery Sample</h1>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr><th colspan="2">TODO List</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="item in itemList">
|
||||||
|
<td>{{$index + 1}}</td>
|
||||||
|
<td>
|
||||||
|
{{item.name}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">New Item:</label>
|
||||||
|
<input type="text" class="form-control" id="name" placeholder="(Enter Todo Item)" ng-model="item.name">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-default" value="Create" ng-click="create(item)">Create</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="bower_components/angular.js"></script>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
<script src="services.js"></script>
|
||||||
|
<script src="controllers.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
angular.module('TODO', [
|
||||||
|
'TODO.controllers',
|
||||||
|
'TODO.services'
|
||||||
|
]);
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
angular.module('TODO.controllers', []).
|
||||||
|
controller('todoController', function ($scope, todoApi) {
|
||||||
|
$scope.itemList = [];
|
||||||
|
$scope.item = {};
|
||||||
|
|
||||||
|
$scope.refresh = function (item) {
|
||||||
|
todoApi.getItems().success(function (response) {
|
||||||
|
$scope.itemList = response;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.create = function (item) {
|
||||||
|
todoApi.create(item).success(function (response) {
|
||||||
|
$scope.item = {};
|
||||||
|
$scope.refresh();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load initial items
|
||||||
|
$scope.refresh();
|
||||||
|
});
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
angular.module('TODO.services', []).
|
||||||
|
factory('todoApi', function ($http) {
|
||||||
|
|
||||||
|
var todoApi = {};
|
||||||
|
|
||||||
|
todoApi.getItems = function () {
|
||||||
|
return $http({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/items'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
todoApi.create = function (item) {
|
||||||
|
return $http({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/items',
|
||||||
|
data: item
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return todoApi;
|
||||||
|
});
|
||||||
|
|
@ -33,7 +33,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection ConfigureAntiforgery(
|
public static IServiceCollection AddAntiforgery(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
Action<AntiforgeryOptions> setupAction)
|
Action<AntiforgeryOptions> setupAction)
|
||||||
{
|
{
|
||||||
|
|
@ -42,12 +42,13 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||||
throw new ArgumentNullException(nameof(services));
|
throw new ArgumentNullException(nameof(services));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setupAction == null)
|
services.AddAntiforgery();
|
||||||
|
|
||||||
|
if (setupAction != null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(setupAction));
|
services.Configure(setupAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
services.Configure(setupAction);
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue