Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
github.com/blowdart/AspNetAuthenticationWorkshop
https://github.com/dotnet-presentations
This is walk through for a ASP.NET Core Authentication Lab, targeted against ASP.NET Core 2.1 and VS2017/VS Code.
This lab uses the Model-View-Controller template as that's what everyone has been using up until now and it's the most familiar starting point for the vast majority of people.
Official authentication documentation is at https://docs.microsoft.com/en-us/aspnet/core/security/authentication/.
An authorization lab is available at https://github.com/blowdart/AspNetAuthorizationWorkshop/tree/core2
We're going to start with the command line.
dotnet new console
dotnet run
and you will see "Hello World"Let's examine what's been created. The directory contains two files
authenticationlab.csproj
Program.cs
Type code .
and VS Code will open the directory. It may install some things that are missing. Explore the two files.
program.cs
contains the instructions to output 'Hello World'.Let's turn this into a web application. .NET Core is the core libraries and runtime. ASP.NET Core adds support for web applications, including Kestrel a web server.
First let's add ASP.NET Core to the application.
csproj
file and add the following after the closing PropertyGroup
element <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1" />
</ItemGroup>
csproj
should now look like this<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1" />
</ItemGroup>
</Project>
csproj
file. If you're using Visual Studio or VS Code you may be prompted to restore packages, choose yes. If you're using the command line and an editor that makes you reconsider your life choices like VIM enter dotnet restore
at the command line. No, I can't help you exit VIM.program.cs
file. Change the contents of this file to be as followsusing System;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace authenticationlab
{
class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
}
.UseStartup<Startup>()
is having a bad time, it's looking for a class called
startup
. So let's add that; create a new file called startup.cs
and paste the following into itusing System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace authenticationlab
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}
dotnet build
dotnet run
For this lab we're going to setup an app which uses Google for login.
First we need to configure HTTPS.
Check if you have a developer certificate run dotnet dev-certs https -c -v
in your command line. If you have a certificate you will see "A valid certificate was found."
If no certificate was fund run dotnet dev-certs https --trust
. You will see a popup from Windows asking you if you want to trust a certificate for localhost
. Click yes and you will now have a certificate. If you're on Linux trust will not work at certificate generation time, you'll have to trust it in whatever browser you use. If you're using Firefox you will also have to trust it in the browser as Firefox does not honour the OS certificate settings.
Run your application again, but this time browser to https://localhost:5001 and you should be able to connect over HTTPS.
You can force HTTP connections up to HTTPS by adding app.UseHttpsRedirection();
at the start of your Configure()
method.
Next we will create an app with Google to support Google sign in
Navigate to https://developers.google.com/identity/sign-in/web/sign-in and click the Configure A Project
button. Create a new project called CoreAuthenticationLab.
At the Configure your OAuth client dialog select Web Server
from the Where are you calling from?
drop down and enter https://localhost:5001/signin-google as the authorized redirect URI.
Click Create
.
Make a note of your Client ID and Client secret from the resulting screen then click the API Console link.
Click the Enable APIs and services
button and in the search box enter Google+ API
then select it. Click Enable
.
Navigate to https://console.developers.google.com/apis/dashboard and in the drop down at the top of the screen choose your
Return to your code and replace startup.cs
with the following code, putting your Client ID and Secret in the options properties.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace authenticationlab
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options =>
{
options.ClientId = "**CLIENT ID**";
options.ClientSecret = "**CLIENT SECRET**";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHttpsRedirection();
app.UseAuthentication();
app.Run(async (context) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync();
}
await context.Response.WriteAsync("Hello "+context.User.Identity.Name+"!\r");
});
}
}
}
WriteAsync
callcontext.Response.Headers.Add("Content-Type", "text/plain");
ILogger
called _logger
inside the Startup
class.ILoggerFactory
and creates and assigns a logger to _logger
. (You will need to add a using
reference to Microsoft.Extensions.Logging
if your editor isn't doing that work for you.) private ILogger _logger;
public Startup(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<Startup>();
}
using Microsoft.AspNetCore.Authentication.OAuth;
if your editor doesn't prompt you to do this).options.Events = new OAuthEvents()
{
OnRedirectToAuthorizationEndpoint = context =>
{
_logger.LogInformation("Redirecting to {0}", context.RedirectUri);
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
_logger.LogInformation("Something went horribly wrong.");
return Task.CompletedTask;
},
OnTicketReceived = context =>
{
_logger.LogInformation("Ticket received.");
return Task.CompletedTask;
},
OnCreatingTicket = context =>
{
_logger.LogInformation("Creating tickets.");
return Task.CompletedTask;
}
};
OnRedirectToAuthorizationEndpoint
default implementation calls the redirect - this isn't happening in your code any more, so add it back;OnRedirectToAuthorizationEndpoint = context =>
{
_logger.LogInformation("Redirecting to {0}", context.RedirectUri);
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
},
OnCreatingTicket()
. There's some useful stuff in there, like the Google access token. What if I want to save that?OnCreatingTicket
as claims.const
values in the Startup
class like soprivate const string AccessTokenClaim = "urn:tokens:google:accesstoken";
OnCreatingTicket
event let's use these names to create some new claims, with the appropriate valuesOnCreatingTicket = context =>
{
var identity = (ClaimsIdentity)context.Principal.Identity;
identity.AddClaim(new Claim(AccessTokenClaim, context.AccessToken));
_logger.LogInformation("Creating tickets.");
return Task.CompletedTask;
}
app.Run()
lambda after the greeting;var claimsIdentity = (ClaimsIdentity)context.User.Identity;
var accessTokenClaim = claimsIdentity.Claims.FirstOrDefault(x => x.Type == AccessTokenClaim);
if (accessTokenClaim != null)
{
await context.Response.WriteAsync("Google access claims have persisted\r");
}
app.Run()
lambda with the following, adding your API key in the appropriate place;app.UseHttpsRedirection();
app.UseAuthentication();
app.Run(async (context) =>
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync();
}
context.Response.Headers.Add("Content-Type", "text/html");
await context.Response.WriteAsync("<html><body>\r");
var claimsIdentity = (ClaimsIdentity)context.User.Identity;
var nameIdentifier = claimsIdentity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value;
var googleApiKey = "**API KEY**";
if (!string.IsNullOrEmpty(nameIdentifier))
{
string jsonUrl = $"https://www.googleapis.com/plus/v1/people/{nameIdentifier}?fields=image&key={googleApiKey}";
using (var httpClient = new HttpClient())
{
var s = await httpClient.GetStringAsync(jsonUrl);
dynamic deserializeObject = JsonConvert.DeserializeObject(s);
var thumbnailUrl = (string)deserializeObject.image.url;
if (thumbnailUrl != null && !string.IsNullOrWhiteSpace(thumbnailUrl))
{
await context.Response.WriteAsync(
string.Format($"<img src=\"{thumbnailUrl}\"></img>"));
}
}
}
await context.Response.WriteAsync("</body></html>\r");
});
}
}
}
How is this all hanging together?
Remote authentication is just that. Remote. There is no persistence mechanism to allow it to be reused.
Persistent authentication is authentication whose information is sent with every request, like Basic authentication, Digest authentication, Certificate authentication or cookie based tokens.
Examine the authentication configuration in ConfigureServices()
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
Default*
options on the AuthenticationService
configuration;
DefaultScheme
is, well, the default. It provides every event handler, unless you override the other events.AuthenticationScheme
is the provider that runs on every request and attempts to construction an identity from information in the request.ChallengeScheme
is the provider that will handle challenges, the event that happens when authorization is required and there's no identity on the request.ForbidScheme
is the provider that handles forbid events, which fire when authorization happens and the current identity fails the authorization check.DefaultSignInScheme
and DefaultSignOutScheme
indicate the provider which will handle SignIn
and SignOut
calls.Fire up your browser and look at the asp.net cookie that's been issued, '.AspNetCore.Cookies'
What does the cookie contain?
What's the expiry on the cookie?
How do you think the cookie is protected?
Let's configure that cookie; first let's set a permanent expiry date on it so it persist over browser closes
.AddCookie(options =>
{
options.Cookie.Expiration = new System.TimeSpan(0, 15, 0);
})
.AddCookie(options =>
{
options.ExpireTimeSpan = new System.TimeSpan(0, 15, 0);
options.SlidingExpiration = true;
})
OnValidatePrincipal
event.private static int RequestCount = 0;
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
if (context.Request.Path.HasValue && context.Request.Path == "/")
{
System.Threading.Interlocked.Increment(ref RequestCount);
}
if (RequestCount % 5 == 0)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
context.ReplacePrincipal(newPrincipal);
context.ShouldRenew = true;
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\"))
.ProtectKeysWithCertificate("thumbprint");
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.SetApplicationName("shared app name");
}
AuthenticateAsync()
.IClaimsTransformation
.class ClaimsTransformer : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
((ClaimsIdentity)principal.Identity).AddClaim(
new Claim("transformedOn", DateTime.Now.ToString()));
return Task.FromResult(principal);
}
}
ConfigureServices()
public void ConfigureServices(IServiceCollection services)
{
// Other service config removed
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
}
AuthenticateAsync()
is called, so it would add the "now" claim every time it runs, which is probably not what you want - claims transformers need to be defensive, for examplepublic Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = (ClaimsIdentity)principal.Identity;
if (claimsIdentity.Claims.FirstOrDefault(x => x.Type == "transformedOn") == null)
{
((ClaimsIdentity)principal.Identity).AddClaim(
new Claim("transformedOn", System.DateTime.Now.ToString()));
}
return Task.FromResult(principal);
}
app.Run()
to see the claim; note that it updates on every run, it's not persisted into the cookie, it runs after the cookie has been written.app.Run()
lambda isn't really a sustainable development strategy, so let's add ASP.NET MVC to the mix.ConfigureServices()
add a call to services.AddMvc();
Configure()
method with the following;public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Controllers
folder and inside the new folder create a new file HomeController.cs
.
Put the following code in the file.using Microsoft.AspNetCore.Mvc;
namespace authenticationlab.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
Views
folder in your application folder, then create a Home
folder inside the Views
folder.Home
folder create a file called Index.cshtml
and change the contents to be<!DOCTYPE html>
<html>
<head>
<title>ASP.NET MVC</title>
<meta charset="utf-8" />
</head>
<body>
<p>
Hello world!
</p>
</body>
</html>
Finally clean your project by executing dotnet clean
in the project directory, or by using Visual Studio's Clean
context menu item on the project file.
Note if you're using Visual Studio you will notice it now tries to be clever and hooks up IIS Express to host your application,
as well as assigning random ports. This can be controlled via the launchsettings.json
file it created.
Change the applicationURL
property in the authenticationLab profile to be "https://localhost:5001;http://localhost:5000"
and delete the IIS Express
profile.
Run your project and browse to the site and you should see Hello World
.
Now, let's get back to where we were, first let's see who the current user is. Open up Index.cshtml
and replace Hello World
with Hello @User.Identity.Name
. Build and run your application and browse to it. This time you will only see Hello
. Why?
We need to add the authentication process back to the application.
Open your HomeController.cs
file and add an [Authorize]
attribute to the Index action. You will also need to add a using
statement for Microsoft.AspNetCore.Authorization
. Your controller should look like this
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace authenticationlab.Controllers
{
public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return View();
}
}
}
index.cshtml
to be<!DOCTYPE html>
<html>
<head>
<title>ASP.NET MVC</title>
<meta charset="utf-8" />
</head>
<body>
<p>
Hello @User.Identity.Name
</p>
</body>
</html>
[Authorize]
attribute tells MVC that all requests to the action need to be authorized.
The default authorization rule is any authenticated user,
in fact any authorization rule requires an authenticated user so before authorization can happen authentication must take place,
so it's now doing what we manually previous in these lines of codeif (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync();
}
One thing to note is that any output into a view is HTML attribute encoded by default,
so even if we got a user name which had <script>
in it, it's going to end up as
&lt;script&gt;
in the view output.
If we wanted to get out pretty google profile picture back again we could go and grab it in the controller, shove it in a model, and pass it into the view. So let's do that.
Create a Models
folder in the root of your project and inside the folder create a new file, IndexViewModel.cs
. Paste the following code into the file
namespace authenticationlab.Models
{
public class IndexViewModel
{
public string ProfilePictureUri { get; set; }
}
}
Index
action in the HomeController
to create an instance of the model,
and assign the profile Uri to the property on the model using the code that you had in app.Run()
.
Note that inside on the Controller
base class are a number of properties, including User
so you don't need to go looking for the request context.
Your controller code should look some this;using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace authenticationlab.Controllers
{
[Authorize]
public class HomeController : Controller
{
public async Task<IActionResult> Index()
{
var model = new Models.IndexViewModel();
var claimsIdentity = (ClaimsIdentity)User.Identity;
var nameIdentifier = claimsIdentity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value;
var googleApiKey = "AIzaSyBZyzSW3_6G1DsvTtWF4PTSOy5ENcmvnMg";
if (!string.IsNullOrEmpty(nameIdentifier))
{
string jsonUrl = $"https://www.googleapis.com/plus/v1/people/{nameIdentifier}?fields=image&key={googleApiKey}";
using (var httpClient = new HttpClient())
{
var s = await httpClient.GetStringAsync(jsonUrl);
dynamic deserializeObject = JsonConvert.DeserializeObject(s);
var thumbnailUrl = (string)deserializeObject.image.url;
if (thumbnailUrl != null && !string.IsNullOrWhiteSpace(thumbnailUrl))
{
model.ProfilePictureUri = thumbnailUrl;
}
}
}
return View(model);
}
}
}
@model authenticationlab.Models.IndexViewModel;
<!DOCTYPE html>
<html>
<head>
<title>ASP.NET MVC</title>
<meta charset="utf-8" />
</head>
<body>
<p>
Hello @User.Identity.Name
</p>
@if (!string.IsNullOrEmpty(Model.ProfilePictureUri))
{
<p>
<img src="@Model.ProfilePictureUri" />
</p>
}
</body>
</html>
startup.cs
remove add the .AddAuthentication()
call and the handlers hanging off it.startup.cs
should now look likeusing Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace authenticationlab
{
public class Startup
{
private ILogger _logger;
public Startup(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<Startup>();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace authenticationlab.Controllers
{
public class HomeController : Controller
{
[Authorize]
public IActionResult Index()
{
return View();
}
}
}
<!DOCTYPE html>
<html>
<head>
<title>ASP.NET MVC</title>
<meta charset="utf-8" />
</head>
<body>
<p>
Hello @User.Identity.Name
</p>
</body>
</html>
If you want you can also delete the model class.
Now if you run the application the [Authorize]
attribute will cause an error,
InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
because there is no authentication handlers in the pipeline. So let's write one.
At its simplest an authentication handler is an implementation of AuthenticationHandler
,
an associated options class and, if you're feeling helpful an events class,
a helper to support app.Add*
and a default class to hold a scheme name.
Let's start with the handler itself. Add a new file, AwfulQueryStringAuthenticationHandler.cs
to your project.
Make the class inherit from AuthenticationHandler
. but, this needs an options class.
As we're not going to implement options let's just use the base class AuthenticationSchemeOptions
.
A handler needs to implement HandleAuthenticateAsync()
, so we can put in a default implementation;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
namespace authenticationlab
{
public class AwfulQueryStringAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
throw new NotImplementedException();
}
}
}
public AwfulQueryStringAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
using System;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace authenticationlab
{
public class AwfulQueryStringAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public AwfulQueryStringHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
throw new NotImplementedException();
}
}
}
AwfulQueryStringAuthenticationDefaults.cs
and add the following codenamespace authenticationlab
{
public static class AwfulQueryStringAuthenticationDefaults
{
public const string AuthenticationScheme = "Awful";
}
}
services.AddX()
support.
This is provided by extension methods on AuthenticationBuilder
.
Create a new file called AwfulQueryStringAuthenticationExtensions.cs
and put the following code into it.using System;
using Microsoft.AspNetCore.Authentication;
using authenticationlab;
namespace Microsoft.AspNetCore.Builder
{
public static class AwfulQueryStringAuthenticationAppBuilderExtensions
{
public static AuthenticationBuilder AddAwfulQueryString(
this AuthenticationBuilder builder)
=> builder.AddAwfulQueryString(
AwfulQueryStringAuthenticationDefaults.AuthenticationScheme);
public static AuthenticationBuilder AddAwfulQueryString(
this AuthenticationBuilder builder,
string authenticationScheme)
=> builder.AddAwfulQueryString(
authenticationScheme,
configureOptions: null);
public static AuthenticationBuilder AddAwfulQueryString(
this AuthenticationBuilder builder,
Action<AuthenticationSchemeOptions> configureOptions)
=> builder.AddAwfulQueryString(
AwfulQueryStringAuthenticationDefaults.AuthenticationScheme,
configureOptions);
public static AuthenticationBuilder AddAwfulQueryString(
this AuthenticationBuilder builder,
string authenticationScheme,
Action<AuthenticationSchemeOptions> configureOptions)
{
return builder.AddScheme<
AuthenticationSchemeOptions,
AwfulQueryStringAuthenticationHandler>(
authenticationScheme,
configureOptions);
}
}
}
namespace
for this class is Microsoft.AspNetCore.Builder
which will allow it to appear in ConfigureServices()
.ConfigureServices()
;public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AwfulQueryStringAuthenticationDefaults.AuthenticationScheme)
.AddAwfulQueryString();
services.AddMvc();
}
If you now run your application and browse to the web page you will see your handler gets called, and throws the NotImplementedException
.
So, let's add an implementation.
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string usernameParameter = Request.Query["username"];
if (!string.IsNullOrEmpty(usernameParameter))
{
var identity = new ClaimsIdentity(Scheme.Name);
identity.AddClaim(
new Claim(
ClaimTypes.Name,
usernameParameter,
ClaimValueTypes.String,
Options.ClaimsIssuer));
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.NoResult();
}
Run your code and browse to the site.
You'll see that there's nothing being rendered when you visit the home page.
Open your browser tooling and turn on network capture and refresh -
you're getting a 401 back because there's no username
query string parameter and there
are no other handlers which could construct an identity.
Try adding a username
parameter to the query string, with a value. and browse.
You have an authenticated user. Let's never talk of this again
If you wanted to map status codes to error messages there is a built in middleware for that,
just add app.UseStatusCodePages();
into your app configuration.
The inbox authorization handlers have events in them. Add an event to your query string handler which allows the handler to pass the inbound user name somewhere else for validation, and then allows or forbids the request based on the results of that call.
Now why not look at the Authorization Workshop?
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.