How to use OpenIddict AuthServer and Web API in same project - asp.net-web-api

I've built an OpenIddict OAuth server using the marvelous guide by Robin van der Knaap although I've actually implemented it using Identity instead of cookies.
I'm also trying to run a Web API from the same project because the end customer only wants a single system to call.
At the moment I'm doing all my endpoint testing in postman.
This user info endpoint in the AuthorisationController works fine:
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/userinfo")]
public async Task<IActionResult> Userinfo()
{
var claimsPrincipal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal;
return Ok(new
{
Name = claimsPrincipal.GetClaim(OpenIddictConstants.Claims.Subject),
Occupation = "Developer",
Age = 43
});
}
But when I try to call this custom Web API controller endpoint (https://<domain.com>/api/Test/):
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return Ok("hello");
}
}
I just get the following error:
System.InvalidOperationException: An identity cannot be extracted from
this request. This generally indicates that the OpenIddict server
stack was asked to validate a token for an endpoint it doesn't manage.
To validate tokens received by custom API endpoints, the OpenIddict
validation handler (e.g
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme or
OpenIddictValidationOwinDefaults.AuthenticationType) must be used
instead. at
OpenIddict.Server.OpenIddictServerHandlers.ValidateAuthenticationDemand.HandleAsync(ProcessAuthenticationContext
context) at
OpenIddict.Server.OpenIddictServerDispatcher.DispatchAsync[TContext](TContext
context) at
OpenIddict.Server.OpenIddictServerDispatcher.DispatchAsync[TContext](TContext
context) at
OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandler.HandleAuthenticateAsync()
at
Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
at
Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext
context, String scheme) at
Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy
policy, HttpContext context) at
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext
context) at
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext
context) at
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
My Program.cs looks like this:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OAuthServer;
using OAuthServer.Data;
using OpenIddict.Server.AspNetCore;
using OpenIddict.Validation.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(connectionString);
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
builder.Services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(options =>
{
// Configure OpenIddict to use the EF Core stores/models.
options.UseEntityFrameworkCore()
.UseDbContext<ApplicationDbContext>();
})
// Register the OpenIddict server components.
.AddServer(options =>
{
options
.AllowClientCredentialsFlow()
.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange()
.AllowRefreshTokenFlow();
options
.SetTokenEndpointUris("/connect/token")
.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo");
// Encryption and signing of tokens TODO: Replace with x.509 cert
options
.AddEncryptionCertificate(CertificateHelper.LoadCertificateFromKeyVault(builder.Configuration["KeyVault:Name"], builder.Configuration["OAuth:EncryptionCertName"]))
.AddSigningCertificate(CertificateHelper.LoadCertificateFromKeyVault(builder.Configuration["KeyVault:Name"], builder.Configuration["OAuth:EncryptionCertName"]))
/*.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()*/
.DisableAccessTokenEncryption();
// Register scopes (permissions)
options.RegisterScopes("api");
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
options
.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.EnableAuthorizationEndpointPassthrough()
.EnableUserinfoEndpointPassthrough();
})
.AddValidation();
builder.Services.AddAuthentication(options => options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
builder.Services.AddHostedService<TestData>();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
How can I make [Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] work on custom API endpoints?
UPDATE
I have a feeling the Web API bit is a red-herring and the issue is something more fundamental. I tried to add an MVC action in the same AuthorisationController:
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
[HttpGet("~/connect/hello")]
public async Task<IActionResult> Hello()
{
return Ok("hi");
}
But that gives the same error.
I think I should probably just be using [Authorize] without specifying the scheme (which is what I was originally trying to do) but that always gives unauthorised.....
I suspect this article has something in it I need, but it's for an old version of OpenIddict (I'm using 3.1.1) and I can't figure out the current behaviour.

Turns out I was just using the wrong scheme on the authorize attribute. I needed to change:
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
to:
[Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
And if you want to avoid adding the scheme in the attribute then you need to add this to your service configuration:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});
NOTE: This seems to conflict with the information on
The OpenIddict ASP.NET Core server cannot be used as the default scheme handler but it's what worked for me.
Although this won't play nice with Identity sign-in (unless I fiddle with the sign-in call) so I left it out.

Related

HttpContext.Session.Abandon() doesn't work in MVC core. Session.clear doesn't log my user out

I get an error that says "ISession does not contain a definition for 'Abandon' and no accessible extension method 'Abandon' accepting a first argument of type 'ISession' could be found".
I have tried using session.clear but even after logging out if I open the website the user is logged in.
This is the error I get
This is how I have implemented Session in my ASP .NET CORE project:
Create a SessionTimeout filter:
public class SessionTimeout : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Session == null ||!context.HttpContext.Session.TryGetValue("UserID", out byte[] val))
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Pages",
action = "SessionTimeout"
}));
}
base.OnActionExecuting(context);
}
}
Register this filter in your Startup.cs:
In your ConfigureServices method:
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
In your Configure add:
app.UseSession();
And finally decorate your class/method with your filter like:
[SessionTimeout]
public class DashboardController : Controller
To destroy your session based on Logout event from your View:
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Login", new { controller = "Pages" });
}
It seems my session is being stored in cookies and not getting cleared/deleted when used session.clear()
so I've used this and it seems to work like a charm.
foreach (var cookie in Request.Cookies.Keys)
{
if (cookie == ".AspNetCore.Session")
Response.Cookies.Delete(cookie);
}
HttpContext.Session.Clear() wasn't working for me on my live site in the Controller for my Account/Logout page.
I found out that setting a href of /Account/Logout/ was the problem. I changed my links to /Account/Logout.
If you're having Session problems in .NET Core 3.1 then try this. It may also account for why I couldn't get Cookie Authentication to work - I gave up in the end and switched to using Sessions.

Dynamic service name with ocelot and consul

I am using Ocelot and API gateway with Consul and the service discovery.
I am registering services in the Consul with dynamic names like : service.name.1234 and service.name.5678
This services are statful and not meant to be scaled at all
Since i am working with Ocelot i would like to be able to route a request to the desired service but since the names are dynamic i would need to use the query string parameter as the service name
Example : http://myapp.com/service/1234
Should be redirected to the container with the name service.name.1234
Is there any way to achive this using both products? or maybe other product?
Thank you
I have been searching for myself for the same solution but found only one comment on GitHub and it helped me a lot
So, you need to create custom middleware that will rewrite Ocelot`s DownstreamRoute:
public static async Task InvokeAsync(HttpContext httpContext, Func<Task> next)
{
var downstreamRoute = httpContext.Items.DownstreamRoute();
var yourServiceName = //get query string parameter from httpContext;
//rewrite any parameter that you want
httpContext.Items.UpsertDownstreamRoute(
new DownstreamRoute(
downstreamRoute.Key,
downstreamRoute.UpstreamPathTemplate,
downstreamRoute.UpstreamHeadersFindAndReplace,
downstreamRoute.DownstreamHeadersFindAndReplace,
downstreamRoute.DownstreamAddresses,
tenantServiceName,
...
));
}
And after that call it in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// some other code
var configuration = new OcelotPipelineConfiguration
{
PreQueryStringBuilderMiddleware = async (ctx, next) =>
{
await RouteContextRetrieverMiddleware.InvokeAsync(ctx, next);
await next.Invoke();
}
};
app.UseOcelot(configuration).GetAwaiter().GetResult();
}

Using Bearer/Jwt authorization without Identity

I'm developing a Web API with Asp 5 and reading some documents about Web API realize I need Bearer authorization.
After searching I can't find any document or sample that use authorization without Aspnet.Identity. I have my own membership and I don't want to use Identity
Should I use Identity library? or is there a way to implement authorization in my membership.
One little side question:
if I'm forced to use Identity how can I change EntityFramework to something like dapper or ADO.NET for my DBContext?
There's already a JWT Bearer middleware, you just need to write something that issues bearer tokens. That's a little more complicated, depending on what you use as your identity store, and as you indicate it's something custom, it's hard to advise on any approach. Creating JWT tokens isn't that hard though;
var now = DateTime.UtcNow;
// Creates new keys automatically, you'd want to store these somewhere
var aes = new AesCryptoServiceProvider();
var signingTokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(
new[]
{
new Claim(JwtRegisteredClaimNames.Aud, "YOURWEBSITEURL") }),
TokenIssuerName = "YourWebSite",
Lifetime = new Lifetime(now, now.AddHours(1)),
SigningCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(aes.Key),
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256")
};
var token = signingTokenHandler.CreateToken(tokenDescriptor);
var tokenAsString = signingTokenHandler.WriteToken(token);
None of the authorization pieces depend on membership at all, they'll work with any authentication middleware. None of the documentation for authorization even refers to Identity at all.
There's an authorization workshop available. You can see in the source for that that no-one does identity appear, it's creating user principals on the fly and then storing them in cookies.
To issue your own JWT tokens, you can use OpenIddict:
project.json
{
"dependencies": {
// ...
"AspNet.Security.OAuth.Validation": "1.0.0-*",
"OpenIddict": "1.0.0-*",
"OpenIddict.EntityFrameworkCore": "1.0.0-*",
"OpenIddict.Mvc": "1.0.0-*"
}
}
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store.
options.UseInMemoryDatabase();
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict();
});
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<DbContext>();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.AddMvcBinders();
// Enable the token endpoint.
options.EnableTokenEndpoint("/connect/token");
// Enable the password flow.
options.AllowPasswordFlow();
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
});
}
public void Configure(IApplicationBuilder app)
{
// Register the validation middleware, that is used to decrypt
// the access tokens and populate the HttpContext.User property.
app.UseOAuthValidation();
// Register the OpenIddict middleware.
app.UseOpenIddict();
app.UseMvcWithDefaultRoute();
}
}
AuthorizationController.cs
public class AuthorizationController : Controller
{
[HttpPost("~/connect/token"), Produces("application/json")]
public IActionResult Exchange(OpenIdConnectRequest request)
{
if (request.IsPasswordGrantType())
{
// Validate the user credentials.
// Note: to mitigate brute force attacks, you SHOULD strongly consider
// applying a key derivation function like PBKDF2 to slow down
// the password validation process. You SHOULD also consider
// using a time-constant comparer to prevent timing attacks.
if (request.Username != "alice#wonderland.com" ||
request.Password != "P#ssw0rd")
{
return Forbid(OpenIdConnectServerDefaults.AuthenticationScheme);
}
// Create a new ClaimsIdentity holding the user identity.
var identity = new ClaimsIdentity(
OpenIdConnectServerDefaults.AuthenticationScheme,
OpenIdConnectConstants.Claims.Name,
OpenIdConnectConstants.Claims.Role);
// Add a "sub" claim containing the user identifier, and attach
// the "access_token" destination to allow OpenIddict to store it
// in the access token, so it can be retrieved from your controllers.
identity.AddClaim(OpenIdConnectConstants.Claims.Subject,
"71346D62-9BA5-4B6D-9ECA-755574D628D8",
OpenIdConnectConstants.Destinations.AccessToken);
identity.AddClaim(OpenIdConnectConstants.Claims.Name, "Alice",
OpenIdConnectConstants.Destinations.AccessToken);
// ... add other claims, if necessary.
var principal = new ClaimsPrincipal(identity);
// Ask OpenIddict to generate a new token and return an OAuth2 token response.
return SignIn(principal, OpenIdConnectServerDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
}
Request
POST /connect/token HTTP/1.1
Host: localhost:7096
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=alice%40wonderland.com&password=P%40ssw0rd
Response
{
"token_type": "Bearer",
"access_token": "CfDJ8Ec0ZpniaHhGg0e0UUvOH9BWZSGrPoEwGd0_Lq2cse-T29YOq985IBiT5fEe5tTSgY1vxq2Z2ZJ7Ikwlpmh0Lrc4x9pqhqHBziUzsP_rkGZkn47TkNkOkzKCwZJZK5x-irH3HROwClFFTq0rgWdb8rZ2xriffNzsby4VwhxhN5soFD435KzmVYkdv-VuaLYo3QiSuexbRi2USVO9LK30vomAG6h2SAxZ7R-jYsXgf0f5gAmdYxg7w3yicv9v8DpUSBiGGRRfymTOnvGEsFJjGuuP8OlY5qzMs6wGaRWkOvCyV2CK_RZF_3TMs7LYCdMQ-dqWY5A03-03OmP8blKzlrKJMDZfrPQHuysbS931xxy8b3kjicfjNLmMHqzQzbUO4fecm4kY8PFnKozojDtqajfTp2bYhxS65bmVYROrswYeUWEKYR6LSdS1K__IDaLoMlLa-Wf6x1wjM2CchzgqbHRF0KEtdL5Ks88dAS44mp9BM6iUOEWyL7VkbazsBdlNciM5ZZB1_6qunufDW_tcaR8",
"expires_in": 3600
}
For more information, you can read this blog post I wrote about OpenIddict: http://kevinchalet.com/2017/01/30/implementing-simple-token-authentication-in-aspnet-core-with-openiddict/

ASP.Net WebAPI OWIN: Why would Request.GetOwinContext() return null?

In my production code we're having a problem where Request.GetOwinContext() always returns null.
I setup a small test WebAPI controller to try and isolate the problem:
public class TestController : ApiController
{
[HttpGet]
public async Task<IHttpActionResult> GetAsyncContext(string provider)
{
if (HttpContext.Current.GetOwinContext() == null)
return this.BadRequest("No HttpContext.Current Owin Context");
if (Request.GetOwinContext() == null)
return this.BadRequest("No Owin Context");
return this.Ok();
}
[HttpGet]
public IHttpActionResult GetContext(string provider)
{
if (HttpContext.Current.GetOwinContext() == null)
return this.BadRequest("No HttpContext.Current Owin Context");
if (Request.GetOwinContext() == null)
return this.BadRequest("No Owin Context");
return this.Ok();
}
}
At first I thought it might have something to do with the action method running asynchronously, but after running the above, it turns out that in both versions, Request.GetOwinContext() returns null.
I am using Microsoft.AspNet.WebApi.Owin.5.1.1 (which is where it seems the GetOwinContext() extension method is defined).
Any ideas on what's happening here???
Another cause (especially after upgrading form ASP.NET MVC4 and / or Empty WebApi Template) is missing Startup.cs file in the root of WebAPI project.
Also, make sure that you have installed Microsoft.Owin.Host.SystemWeb package.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(TestMVC5.Startup))]
namespace TestMVC5
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
I encountered similar issue. To fix this make sure below lines (with call to method CreatePerOwinContext) are there in your Startup.Auth.cs file in ConfigureAuth
method.
Your method may look like
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication(
// clientId: "",
// clientSecret: "");
}

.NET Web API 2 OWIN Bearer Token Authentication direct call

I have a problem with my Web Api Project.
I have files stored in my Database and want to call them directly in a new window to view/save (URL like : /api/Files/5 - 5 beeing the FileId)
I got everthing working with the Bearer Token for my general AJAX requests with AngularJS for normal Data and it works like a charm. For the file I created a Controller that shows the file in the browser with the corresponding MIME-Type. But now that I changed the action to [Authorize] I get an Access Denied which is correct because I didnt pass an access_token in the HTTP-Header.
I did quite some research if it is possible to pass the Token via the querystring but didn't find anything helpful.
Now my plan is to remove the [Authorize] Attribute from my Controller and try to validate the token myself but I don't know how.
Anyone know how I can get it to work?
I implemented bearer token authentication in my app (AngularJS, WebAPI 2) and I had similar problem - I needed to allow downloading files by clicking on a link. When you click on a link headers are not sent. :(
So, I sent the token value in a query string to download a file
.../mywebapp/api/files/getfile/3?access_token=jaCOTrGsaak6Sk0CpPc1...
and set "Authorization" header to the token value in Startup.Auth.cs. Here is the code:
public void ConfigureAuth(IAppBuilder app)
{
//It needs for file downloads
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers.Get("Authorization")))
{
var queryString = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
string token = queryString.Get("access_token");
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", new[] { string.Format("Bearer {0}", token) });
}
}
}
await next.Invoke();
});
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
This feature is already built in - I wrote about it here:
http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/
For ASP .Net Core I did something like this based on Forward's answer
Extension Method
public static void UseQueryStringBearerValidation(this IApplicationBuilder app)
{
//It needs for file downloads
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"].ToString()))
{
var queryString = QueryHelpers.ParseQuery(context.Request.QueryString.Value);
var token = queryString["access_token"].ToString();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", new[] {$"Bearer {token}"});
}
}
}
await next();
});
}
Usage
StartUp.cs -> Configure() method
app.UseCustomExceptionHandler();
app.UseQueryStringBearerValidation(); // <-- add before Jwt Handler
app.UseCustomJwtBearerValidation();
app.AddHttpContextProperties();
app.UseStaticFiles();
app.UseMvc(MiddlewareAppConfiguration.AddRouteMappings);
Although I'm not sure it's a very good idea, you could implementing a DelegatingHandler to achieve what you are looking for.
public class QueryStringBearerToken : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var bearerToken = request.GetQueryNameValuePairs()
.Where(kvp => kvp.Key == "bearerToken")
.Select(kvp => kvp.Value)
.FirstOrDefault();
if(!String.IsNullOrEmpty(bearerToken))
{
request.Headers.Add("Authorization", "Bearer " + bearerToken);
}
return base.SendAsync(request, cancellationToken);
}
}
This handler will look for the query string named "bearerToken" and, if it exists, will add it to the request header for the subsequent handlers / filter to process. You might want to check first if the header is already present and not override in this case. You can add this handler in your configuration phase in the usual fashion:
config.MessageHandlers.Insert(0, new QueryStringBearerToken ());
A request for /YourRoute?bearerToken=theToken will pass in the DelegatingHandler, adding the token passed in the query string to the list of headers in the original request and the regular Bearer Token authentication will look for the header and find it.

Resources