Refactoring authentication to support server-side Blazor using a seamless login flow.

This commit is contained in:
Shaun Walker 2019-07-15 08:30:03 -04:00
parent f3c823e667
commit ce069ed45b
28 changed files with 307 additions and 86 deletions

View File

@ -4,11 +4,12 @@
@using Oqtane.Models @using Oqtane.Models
@using Oqtane.Services @using Oqtane.Services
@using Oqtane.Providers @using Oqtane.Providers
@using Oqtane.Shared
@inherits ModuleBase @inherits ModuleBase
@inject IUriHelper UriHelper @inject IUriHelper UriHelper
@inject IJSRuntime jsRuntime @inject IJSRuntime jsRuntime
@inject IUserService UserService @inject IUserService UserService
@inject ServerAuthenticationStateProvider AuthStateProvider @inject IServiceProvider ServiceProvider
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
@ -28,33 +29,62 @@
<label for="Password" class="control-label">Password: </label> <label for="Password" class="control-label">Password: </label>
<input type="password" name="Password" class="form-control" placeholder="Password" @bind="@Password" /> <input type="password" name="Password" class="form-control" placeholder="Password" @bind="@Password" />
</div> </div>
<div class="form-group">
<div class="form-check form-check-inline">
<label class="form-check-label" for="Remember">Remember Me?</label>&nbsp;
<input type="checkbox" class="form-check-input" name="Remember" @bind="@Remember" />
</div>
</div>
<button type="button" class="btn btn-primary" @onclick="@Login">Login</button> <button type="button" class="btn btn-primary" @onclick="@Login">Login</button>
<NavLink class="btn btn-secondary" href="/">Cancel</NavLink> <button type="button" class="btn btn-secondary" @onclick="@Cancel">Cancel</button>
</div> </div>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
@code { @code {
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } } public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
public string Message { get; set; } = "<div class=\"alert alert-info\" role=\"alert\">Use host/password For Demo Access</div>"; public string Message { get; set; } = "<div class=\"alert alert-info\" role=\"alert\">Use host/password For Demo Access</div>";
public string Username { get; set; } = ""; public string Username { get; set; } = "";
public string Password { get; set; } = ""; public string Password { get; set; } = "";
public bool Remember { get; set; } = false;
private async Task Login() private async Task Login()
{
User user = new User();
user.Username = Username;
user.Password = Password;
user.IsPersistent = Remember;
user = await UserService.LoginUserAsync(user);
if (user.IsAuthenticated)
{ {
User user = new User(); string ReturnUrl = PageState.QueryString["returnurl"];
user.Username = Username;
user.Password = Password; var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
user = await UserService.LoginUserAsync(user); if (authstateprovider == null)
if (user != null)
{ {
AuthStateProvider.NotifyAuthenticationChanged(); // server-side Blazor
UriHelper.NavigateTo(NavigateUrl("", true)); var interop = new Interop(jsRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, username = Username, password = Password, remember = Remember, returnurl = ReturnUrl };
await interop.SubmitForm("/login/", fields);
} }
else else
{ {
Message = "<div class=\"alert alert-danger\" role=\"alert\">User Does Not Exist</div>"; // client-side Blazor
authstateprovider.NotifyAuthenticationChanged();
UriHelper.NavigateTo(NavigateUrl(ReturnUrl, true));
} }
} }
else
{
Message = "<div class=\"alert alert-danger\" role=\"alert\">Login Failed. Please Remember That Passwords Are Case Sensitive.</div>";
}
}
private void Cancel()
{
string ReturnUrl = PageState.QueryString["returnurl"];
UriHelper.NavigateTo(NavigateUrl(ReturnUrl));
}
} }

View File

@ -7,14 +7,12 @@ using Oqtane.Models;
namespace Oqtane.Providers namespace Oqtane.Providers
{ {
public class ServerAuthenticationStateProvider : AuthenticationStateProvider public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
{ {
//private readonly IUserService UserService;
private readonly IUriHelper urihelper; private readonly IUriHelper urihelper;
public ServerAuthenticationStateProvider(IUriHelper urihelper) public IdentityAuthenticationStateProvider(IUriHelper urihelper)
{ {
//this.UserService = UserService;
this.urihelper = urihelper; this.urihelper = urihelper;
} }
@ -25,6 +23,7 @@ namespace Oqtane.Providers
Uri uri = new Uri(urihelper.GetAbsoluteUri()); Uri uri = new Uri(urihelper.GetAbsoluteUri());
string apiurl = uri.Scheme + "://" + uri.Authority + "/~/api/User/authenticate"; string apiurl = uri.Scheme + "://" + uri.Authority + "/~/api/User/authenticate";
User user = await http.GetJsonAsync<User>(apiurl); User user = await http.GetJsonAsync<User>(apiurl);
var identity = user.IsAuthenticated var identity = user.IsAuthenticated
? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.Username) }, "Identity.Application") ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.Username) }, "Identity.Application")
: new ClaimsIdentity(); : new ClaimsIdentity();

View File

@ -32,8 +32,7 @@ namespace Oqtane.Services
public async Task<Alias> GetAliasAsync(int AliasId) public async Task<Alias> GetAliasAsync(int AliasId)
{ {
List<Alias> aliases = await http.GetJsonAsync<List<Alias>>(apiurl); return await http.GetJsonAsync<Alias>(apiurl + "/" + AliasId.ToString());
return aliases.Where(item => item.AliasId == AliasId).FirstOrDefault();
} }
public async Task AddAliasAsync(Alias alias) public async Task AddAliasAsync(Alias alias)

View File

@ -8,6 +8,7 @@ namespace Oqtane.Services
{ {
Task<List<Module>> GetModulesAsync(int PageId); Task<List<Module>> GetModulesAsync(int PageId);
Task<List<Module>> GetModulesAsync(int SiteId, string ModuleDefinitionName); Task<List<Module>> GetModulesAsync(int SiteId, string ModuleDefinitionName);
Task<Module> GetModuleAsync(int ModuleId);
Task AddModuleAsync(Module module); Task AddModuleAsync(Module module);
Task UpdateModuleAsync(Module module); Task UpdateModuleAsync(Module module);
Task DeleteModuleAsync(int ModuleId); Task DeleteModuleAsync(int ModuleId);

View File

@ -7,6 +7,7 @@ namespace Oqtane.Services
public interface IPageService public interface IPageService
{ {
Task<List<Page>> GetPagesAsync(int SiteId); Task<List<Page>> GetPagesAsync(int SiteId);
Task<Page> GetPageAsync(int PageId);
Task AddPageAsync(Page page); Task AddPageAsync(Page page);
Task UpdatePageAsync(Page page); Task UpdatePageAsync(Page page);
Task DeletePageAsync(int PageId); Task DeletePageAsync(int PageId);

View File

@ -10,6 +10,8 @@ namespace Oqtane.Services
Task<User> GetUserAsync(int UserId); Task<User> GetUserAsync(int UserId);
Task<User> GetUserAsync(string Username);
Task AddUserAsync(User user); Task AddUserAsync(User user);
Task UpdateUserAsync(User user); Task UpdateUserAsync(User user);

View File

@ -39,6 +39,11 @@ namespace Oqtane.Services
return modules.ToList(); return modules.ToList();
} }
public async Task<Module> GetModuleAsync(int ModuleId)
{
return await http.GetJsonAsync<Module>(apiurl + "/" + ModuleId.ToString());
}
public async Task AddModuleAsync(Module module) public async Task AddModuleAsync(Module module)
{ {
await http.PostJsonAsync(apiurl, module); await http.PostJsonAsync(apiurl, module);

View File

@ -30,6 +30,11 @@ namespace Oqtane.Services
return pages.OrderBy(item => item.Order).ToList(); return pages.OrderBy(item => item.Order).ToList();
} }
public async Task<Page> GetPageAsync(int PageId)
{
return await http.GetJsonAsync<Page>(apiurl + "/" + PageId.ToString());
}
public async Task AddPageAsync(Page page) public async Task AddPageAsync(Page page)
{ {
await http.PostJsonAsync(apiurl, page); await http.PostJsonAsync(apiurl, page);

View File

@ -32,17 +32,7 @@ namespace Oqtane.Services
public async Task<Site> GetSiteAsync(int SiteId) public async Task<Site> GetSiteAsync(int SiteId)
{ {
List<Site> sites = await http.GetJsonAsync<List<Site>>(apiurl); return await http.GetJsonAsync<Site>(apiurl + "/" + SiteId.ToString());
Site site;
if (sites.Count == 1)
{
site = sites.FirstOrDefault();
}
else
{
site = sites.Where(item => item.SiteId == SiteId).FirstOrDefault();
}
return site;
} }
public async Task AddSiteAsync(Site site) public async Task AddSiteAsync(Site site)

View File

@ -35,8 +35,12 @@ namespace Oqtane.Services
public async Task<User> GetUserAsync(int UserId) public async Task<User> GetUserAsync(int UserId)
{ {
List<User> users = await http.GetJsonAsync<List<User>>(apiurl); return await http.GetJsonAsync<User>(apiurl + "/" + UserId.ToString());
return users.Where(item => item.UserId == UserId).FirstOrDefault(); }
public async Task<User> GetUserAsync(string Username)
{
return await http.GetJsonAsync<User>(apiurl + "/name/" + Username);
} }
public async Task AddUserAsync(User user) public async Task AddUserAsync(User user)

View File

@ -13,17 +13,18 @@ namespace Oqtane.Shared
this.jsRuntime = jsRuntime; this.jsRuntime = jsRuntime;
} }
public Task<string> SetCookie(string name, string value, int days) public Task SetCookie(string name, string value, int days)
{ {
try try
{ {
return jsRuntime.InvokeAsync<string>( jsRuntime.InvokeAsync<string>(
"interop.setCookie", "interop.setCookie",
name, value, days); name, value, days);
return Task.CompletedTask;
} }
catch catch
{ {
return Task.FromResult(string.Empty); return Task.CompletedTask;
} }
} }
@ -41,18 +42,48 @@ namespace Oqtane.Shared
} }
} }
public Task<string> AddCSS(string filename) public Task AddCSS(string filename)
{
try
{
jsRuntime.InvokeAsync<string>(
"interop.addCSS",
filename);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
public Task<string> GetElementByName(string name)
{ {
try try
{ {
return jsRuntime.InvokeAsync<string>( return jsRuntime.InvokeAsync<string>(
"interop.addCSS", "interop.getElementByName",
filename); name);
} }
catch catch
{ {
return Task.FromResult(string.Empty); return Task.FromResult(string.Empty);
} }
} }
public Task SubmitForm(string path, object fields)
{
try
{
jsRuntime.InvokeAsync<string>(
"interop.submitForm",
path, fields);
return Task.CompletedTask;
}
catch
{
return Task.CompletedTask;
}
}
} }
} }

View File

@ -127,7 +127,7 @@ private async Task Refresh()
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated) if (authState.User.Identity.IsAuthenticated)
{ {
user = await UserService.GetCurrentUserAsync(); user = await UserService.GetUserAsync(authState.User.Identity.Name);
} }
} }
else else

View File

@ -25,7 +25,14 @@ namespace Oqtane.Shared
string url = pagestate.Alias.Path + "/" + path; string url = pagestate.Alias.Path + "/" + path;
if (reload) if (reload)
{ {
url += "?reload=true"; if (url.Contains("?"))
{
url += "&reload=true";
}
else
{
url += "?reload=true";
}
} }
return url; return url;
} }

View File

@ -31,8 +31,8 @@ namespace Oqtane.Client
{ {
// register auth services // register auth services
services.AddAuthorizationCore(); services.AddAuthorizationCore();
services.AddScoped<ServerAuthenticationStateProvider>(); services.AddScoped<IdentityAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>()); services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
// register scoped core services // register scoped core services
services.AddScoped<SiteState>(); services.AddScoped<SiteState>();

View File

@ -1,10 +1,14 @@
@using Oqtane.Themes @using Oqtane.Themes
@using Oqtane.Services @using Oqtane.Services
@using Oqtane.Providers @using Oqtane.Providers
@using Oqtane.Shared
@using Oqtane.Models
@using Microsoft.JSInterop
@inherits ThemeObjectBase @inherits ThemeObjectBase
@inject IUriHelper UriHelper @inject IUriHelper UriHelper
@inject IUserService UserService @inject IUserService UserService
@inject ServerAuthenticationStateProvider AuthStateProvider @inject IJSRuntime jsRuntime
@inject IServiceProvider ServiceProvider
<AuthorizeView> <AuthorizeView>
<Authorizing> <Authorizing>
@ -22,13 +26,25 @@
@code { @code {
private void LoginUser() private void LoginUser()
{ {
UriHelper.NavigateTo(NavigateUrl("login")); UriHelper.NavigateTo(NavigateUrl("login?returnurl=" + PageState.Page.Path));
} }
private async Task LogoutUser() private async Task LogoutUser()
{ {
await UserService.LogoutUserAsync(); await UserService.LogoutUserAsync();
AuthStateProvider.NotifyAuthenticationChanged();
UriHelper.NavigateTo(NavigateUrl("", true)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
if (authstateprovider == null)
{
// server-side Blazor
var interop = new Interop(jsRuntime);
await interop.SubmitForm("/logout/", "");
}
else
{
// client-side Blazor
authstateprovider.NotifyAuthenticationChanged();
UriHelper.NavigateTo(NavigateUrl("login", true));
}
} }
} }

View File

@ -29,5 +29,23 @@ window.interop = {
link.href = fileName; link.href = fileName;
head.appendChild(link); head.appendChild(link);
},
submitForm: function (path, fields) {
const form = document.createElement('form');
form.method = 'post';
form.action = path;
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = fields[key];
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
} }
}; };

View File

@ -4,6 +4,8 @@ using Oqtane.Repository;
using Oqtane.Models; using Oqtane.Models;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using System.Security.Claims;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -34,7 +36,7 @@ namespace Oqtane.Controllers
{ {
return users.GetUser(id); return users.GetUser(id);
} }
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
public async Task Post([FromBody] User user) public async Task Post([FromBody] User user)
@ -73,54 +75,48 @@ namespace Oqtane.Controllers
users.DeleteUser(id); users.DeleteUser(id);
} }
// GET api/<controller>/current // GET api/<controller>/name/x
[HttpGet("current")] [HttpGet("name/{name}")]
public User Current() public User GetByName(string name)
{ {
User user = null; return users.GetUser(name);
if (User.Identity.IsAuthenticated)
{
user = users.GetUser(User.Identity.Name);
user.IsAuthenticated = true;
}
return user;
} }
// POST api/<controller>/login // POST api/<controller>/login
[HttpPost("login")] [HttpPost("login")]
public async Task<User> Login([FromBody] User user) public async Task<User> Login([FromBody] User user)
{ {
// TODO: seed host user - this logic should be moved to installation
IdentityUser identityuser = await identityUserManager.FindByNameAsync("host");
if (identityuser == null)
{
var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password");
if (result.Succeeded)
{
users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" });
}
}
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
// seed host user - this logic should be moved to installation
IdentityUser identityuser = await identityUserManager.FindByNameAsync("host");
if (identityuser == null)
{
var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password");
if (result.Succeeded)
{
users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" });
}
}
identityuser = await identityUserManager.FindByNameAsync(user.Username); identityuser = await identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null)
{ {
var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false); var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false);
if (result.Succeeded) if (result.Succeeded)
{ {
await identitySignInManager.SignInAsync(identityuser, false); await identitySignInManager.SignInAsync(identityuser, user.IsPersistent);
user = users.GetUser(identityuser.UserName); user = users.GetUser(identityuser.UserName);
user.IsAuthenticated = true; user.IsAuthenticated = true;
} }
else else
{ {
user = null; user = new Models.User { Username = user.Username, IsAuthenticated = false };
} }
} }
else else
{ {
user = null; user = new Models.User { Username = user.Username, IsAuthenticated = false };
} }
} }
return user; return user;

View File

@ -0,0 +1,3 @@
@page "/login"
@namespace Oqtane.Pages
@model Oqtane.Pages.LoginModel

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Oqtane.Pages
{
[AllowAnonymous]
public class LoginModel : PageModel
{
private readonly UserManager<IdentityUser> identityUserManager;
private readonly SignInManager<IdentityUser> identitySignInManager;
public LoginModel(UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager)
{
identityUserManager = IdentityUserManager;
identitySignInManager = IdentitySignInManager;
}
public async Task<IActionResult> OnPostAsync(string username, string password, bool remember, string returnurl)
{
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
bool validuser = false;
IdentityUser identityuser = await identityUserManager.FindByNameAsync(username);
if (identityuser != null)
{
var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, password, false);
if (result.Succeeded)
{
validuser = true;
}
}
if (validuser)
{
var claims = new List<Claim>{ new Claim(ClaimTypes.Name, username) };
var claimsIdentity = new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme);
var authProperties = new AuthenticationProperties{IsPersistent = remember};
await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, new ClaimsPrincipal(claimsIdentity), authProperties);
}
return LocalRedirect(Url.Content("~/" + returnurl));
}
}
}

View File

@ -0,0 +1,3 @@
@page "/logout"
@namespace Oqtane.Pages
@model Oqtane.Pages.LogoutModel

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Oqtane.Models;
namespace Oqtane.Pages
{
[IgnoreAntiforgeryToken(Order = 1001)]
[AllowAnonymous]
public class LogoutModel : PageModel
{
public async Task<IActionResult> OnPostAsync()
{
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
return LocalRedirect(Url.Content("~/"));
}
}
}

View File

@ -14,6 +14,7 @@
<link href="css/site.css" rel="stylesheet" /> <link href="css/site.css" rel="stylesheet" />
</head> </head>
<body> <body>
@(Html.AntiForgeryToken())
<app>@(await Html.RenderComponentAsync<App>())</app> <app>@(await Html.RenderComponentAsync<App>())</app>
<script src="js/site.js"></script> <script src="js/site.js"></script>

View File

@ -52,6 +52,7 @@ namespace Oqtane.Repository
{ {
/// determine if this module implements IModule /// determine if this module implements IModule
Type moduletype = assembly.GetTypes() Type moduletype = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(ModuleType)) .Where(item => item.Namespace.StartsWith(ModuleType))
.Where(item => item.GetInterfaces().Contains(typeof(IModule))) .Where(item => item.GetInterfaces().Contains(typeof(IModule)))
.FirstOrDefault(); .FirstOrDefault();

View File

@ -24,7 +24,7 @@ namespace Oqtane.Repository
aliasname = accessor.HttpContext.Request.Host.Value; aliasname = accessor.HttpContext.Request.Host.Value;
string path = accessor.HttpContext.Request.Path.Value; string path = accessor.HttpContext.Request.Path.Value;
string[] segments = path.Split('/'); string[] segments = path.Split('/');
if (segments[1] != "~") if (segments[0] == "api" && segments[1] != "~")
{ {
aliasname += "/" + segments[1]; aliasname += "/" + segments[1];
} }

View File

@ -52,6 +52,7 @@ namespace Oqtane.Repository
{ {
/// determine if this theme implements ITheme /// determine if this theme implements ITheme
Type themeType = assembly.GetTypes() Type themeType = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(Namespace)) .Where(item => item.Namespace.StartsWith(Namespace))
.Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault(); .Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault();
if (themeType != null) if (themeType != null)
@ -105,7 +106,10 @@ namespace Oqtane.Repository
theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";"); theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";");
} }
// containers // containers
Type[] containertypes = assembly.GetTypes().Where(item => item.Namespace.StartsWith(Namespace)).Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray(); Type[] containertypes = assembly.GetTypes()
.Where(item => item.Namespace != null)
.Where(item => item.Namespace.StartsWith(Namespace))
.Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray();
foreach (Type containertype in containertypes) foreach (Type containertype in containertypes)
{ {
theme.ContainerControls += (containertype.FullName + ", " + typename[1] + ";"); theme.ContainerControls += (containertype.FullName + ", " + typename[1] + ";");

View File

@ -17,12 +17,9 @@ using System.Runtime.Loader;
using Oqtane.Services; using Oqtane.Services;
using System.Net.Http; using System.Net.Http;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Oqtane.Client;
using Oqtane.Shared; using Oqtane.Shared;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Oqtane.Providers;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace Oqtane.Server namespace Oqtane.Server
{ {
@ -47,25 +44,27 @@ namespace Oqtane.Server
services.AddRazorPages(); services.AddRazorPages();
services.AddServerSideBlazor(); services.AddServerSideBlazor();
// server-side Blazor does not register HttpClient by default // setup HttpClient for server side in a client side compatible fashion ( with auth cookie )
if (!services.Any(x => x.ServiceType == typeof(HttpClient))) if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{ {
// setup HttpClient for server side in a client side compatible fashion
services.AddScoped<HttpClient>(s => services.AddScoped<HttpClient>(s =>
{ {
// creating the URI helper needs to wait until the JS Runtime is initialized, so defer it. // creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var uriHelper = s.GetRequiredService<IUriHelper>(); var uriHelper = s.GetRequiredService<IUriHelper>();
return new HttpClient var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Identity.Application"];
var client = new HttpClient(new HttpClientHandler { UseCookies = false });
if (authToken != null)
{ {
BaseAddress = new Uri(uriHelper.GetBaseUri()) client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Identity.Application=" + authToken);
}; }
client.BaseAddress = new Uri(uriHelper.GetBaseUri());
return client;
}); });
} }
// register auth services // register auth services
services.AddAuthorizationCore(); services.AddAuthorizationCore();
services.AddScoped<ServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>());
// register scoped core services // register scoped core services
services.AddScoped<SiteState>(); services.AddScoped<SiteState>();

View File

@ -20,6 +20,14 @@ window.interop = {
} }
return ""; return "";
}, },
getElementByName: function (name) {
var elements = document.getElementsByName(name);
if (elements.length) {
return elements[0].value;
} else {
return "";
}
},
addCSS: function (fileName) { addCSS: function (fileName) {
var head = document.head; var head = document.head;
var link = document.createElement("link"); var link = document.createElement("link");
@ -29,5 +37,23 @@ window.interop = {
link.href = fileName; link.href = fileName;
head.appendChild(link); head.appendChild(link);
},
submitForm: function (path, fields) {
const form = document.createElement('form');
form.method = 'post';
form.action = path;
for (const key in fields) {
if (fields.hasOwnProperty(key)) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = fields[key];
form.appendChild(hiddenField);
}
}
document.body.appendChild(form);
form.submit();
} }
}; };

View File

@ -14,5 +14,7 @@ namespace Oqtane.Models
public string Password { get; set; } public string Password { get; set; }
[NotMapped] [NotMapped]
public bool IsAuthenticated { get; set; } public bool IsAuthenticated { get; set; }
[NotMapped]
public bool IsPersistent { get; set; }
} }
} }