Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions FileSystemWeb/ClientApp/src/Helpers/API.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,69 @@
import formatUrl from './formatUrl';

export default class API {
static antiforgaryToken = null;
static config = null;

static fetch(resource, { path, query, method, body, headers, ...options } = {}) {
static async fetch(resource, { path, query, method = 'GET', body, headers, ...options } = {}) {
let antiforgaryHeaders = null;
if (method !== 'GET') {
if (!API.antiforgaryToken || true) {
const antiforgary = await API.getAntiforgary();
if (!antiforgary.ok) {
return antiforgary;
}
API.antiforgaryToken = await antiforgary.text();
}
antiforgaryHeaders = {
'X-CSRF-TOKEN-HEADERNAME': API.antiforgaryToken,
};
}

const isFormData = body instanceof FormData;
const contentType = (!isFormData && method && method !== 'GET' && body) ? 'application/json;charset=utf-8' : undefined;
const contentType = (!isFormData && method !== 'GET' && body) ? 'application/json;charset=utf-8' : undefined;
return window.fetch(`/api${formatUrl({ resource, path, query })}`, {
credentials: 'include',
method,
headers: {
...(contentType ? { 'Content-Type': contentType } : null),
...antiforgaryHeaders,
...headers,
},
body: body && !isFormData ? JSON.stringify(body) : body,
...options,
});
}

static login(username, password, keepLoggedIn) {
return this.fetch('/auth/login', {
method: 'POST',
body: {
Username: username,
Password: password,
KeepLoggedIn: keepLoggedIn,
},
});
static async login(username, password, keepLoggedIn) {
try {
API.antiforgaryToken = null;
return await this.fetch('/auth/login', {
method: 'POST',
body: {
Username: username,
Password: password,
KeepLoggedIn: keepLoggedIn,
},
});
} finally {
API.antiforgaryToken = null;
}
}

static logout() {
return this.fetch('/auth/logout', {
method: 'POST',
static async logout() {
try {
API.antiforgaryToken = null;
return await this.fetch('/auth/logout', {
method: 'POST',
});
} finally {
API.antiforgaryToken = null;
}
}

static getAntiforgary() {
return this.fetch('/auth/antiforgary', {
method: 'GET',
});
}

Expand Down
11 changes: 10 additions & 1 deletion FileSystemWeb/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using FileSystemCommon.Models.Auth;
using FileSystemWeb.Models;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
Expand All @@ -12,10 +13,12 @@ namespace FileSystemWeb.Controllers
public class AuthController : ControllerBase
{
private readonly SignInManager<AppUser> signInManager;
private readonly IAntiforgery antiforgery;

public AuthController(SignInManager<AppUser> signInManager)
public AuthController(SignInManager<AppUser> signInManager, IAntiforgery antiforgery)
{
this.signInManager = signInManager;
this.antiforgery = antiforgery;
}

[HttpPost("login")]
Expand All @@ -33,5 +36,11 @@ public async Task<ActionResult> Logout()
await signInManager.SignOutAsync();
return Ok();
}

[HttpGet("antiforgary")]
public ActionResult GetAntiforgary()
{
return Ok(antiforgery.GetAndStoreTokens(HttpContext).RequestToken);
}
}
}
37 changes: 25 additions & 12 deletions FileSystemWeb/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
Expand Down Expand Up @@ -64,7 +65,25 @@ public void ConfigureServices(IServiceCollection services)
config.Cookie.HttpOnly = true;
});

services.AddControllersWithViews();
services.AddAntiforgery(options =>
{
// Set Cookie properties using CookieBuilder properties�.
options.Cookie.Name = "fs_csrf";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.Expiration = null;
options.Cookie.MaxAge = null;
options.FormFieldName = "AntiforgeryFieldname";
options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
options.SuppressXFrameOptionsHeader = false;
});

services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});

// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; });
Expand All @@ -91,17 +110,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAuthentication();
app.UseAuthorization();

if (!env.IsDevelopment())
{
string sslCertificateDirPath = Path.Combine(Directory.GetCurrentDirectory(), @".well-known");
if (!Directory.Exists(sslCertificateDirPath)) Directory.CreateDirectory(sslCertificateDirPath);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(sslCertificateDirPath),
RequestPath = new PathString("/.well-known"),
ServeUnknownFileTypes = true // serve extensionless files
});
}
//app.Use((context, next) =>
//{
// context.Response.Headers.Add("Content-Security-Policy", "default-src 'self' 'unsafe-inline';");
// return next();
//});

app.UseEndpoints(endpoints =>
{
Expand Down