How can I stop a request in Ocelot's PreAuthenticationMiddleware? - c#

I have to do some checks on the request's jwt before ocelot authentication, so I'm doing them within the PreAuthenticationMiddleware like this:
var config = new OcelotPipelineConfiguration
{
PreAuthenticationMiddleware = async (ctx, next) =>
{
var jwt = ctx.HttpContext.Request.Headers["Authorization"];
if (jwtIsOk(jwt)) next.Invoke();
// else ...
}
};
app.UseOcelot(config).Wait();
Instead of not-invoking next, is it possible to return a custom response?
I'd like to return a 401 error

I think this is what you are looking for :
var configuration = new OcelotPipelineConfiguration
{
PreAuthenticationMiddleware = async (ctx, next) =>
{
if (xxxxxx)
{
ctx.Errors.Add(new UnauthenticatedError("Your message"));
return;
}
await next.Invoke();
}
};
In this case I'm using UnauthenticatedError(an Ocelot's class), it will ends with 401. There are more like this. You can also create your own one inheriting from Ocelot.Errors.Error.

Related

SignalR hub with Bearer authentication

I have a problem. I have in my API JWT Bearer authentication. I try to use SignalR hub with authentication but it doesn't work for me. I think I tried everything.
I have something like this:
.AddJwtBearer(conf =>
{
conf.RequireHttpsMetadata = false;
conf.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
conf.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// THIS DOESN'T WORK - empty string
//var accessToken = context.Request.Query["access_token"];
var accessToken2 = context.Request.Headers["Authorization"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken2) &&
(path.StartsWithSegments("/DebateHub")))
{
// Read the token out of the query string
context.Token = accessToken2;
}
// return Task.CompletedTask;
return Task.FromResult<object>(null);
}
};
});
Register hub:
app.UseEndpoints(endpoints =>
{
endpoints.MapAreaControllerRoute(
name: "AreaAdmin",
areaName: "Admin",
pattern: "api/admin/{controller}/{action}");
endpoints.MapAreaControllerRoute(
name: "AreaMobile",
areaName: "Mobile",
pattern: "api/mobile/{controller}/{action}");
endpoints.MapControllers();
endpoints.MapHub<DebateHub>("/DebateHub");
endpoints.MapHub<OnlineCountHub>("/onlinecount");
});
Hub code:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class DebateHub : Microsoft.AspNetCore.SignalR.Hub
{
public override Task OnConnectedAsync()
{
string name = Context.User.Identity.Name;
Groups.AddToGroupAsync(Context.ConnectionId, name);
return base.OnConnectedAsync();
}
}
Client example:
var uri = "https://localhost:44275/DebateHub";
var connection = new HubConnectionBuilder()
.WithUrl(uri,options =>
{
options.AccessTokenProvider = () => Task.FromResult("some_token");
})
.Build();
connection.StartAsync().Wait();
It doesn't work. I still have unauthorized when I try to connect to my DebateHub. All other controllers work with my authentication ok.
What am I doing wrong?
I'm not sure but I think that you should use cookies to authorize to hub.
Look here
You must uncomment this part of your code;
//var accessToken = context.Request.Query["access_token"];
when hub connection request comes to the server it only sets the token in 'context.Request.Query', as microsoft docs states not in context.Request.Headers["Authorization"].
The query string is used on browsers when connecting with WebSockets and Server-Sent Events due to browser API limitations.
Confirm in chrome network tab request headers to see where it is being sent.
Alternatively you can use this middleware in startup configure method which dose same thing by taking the token from query and setting it where expected to be.
app.Use(async (context, next) =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken))
{
context.Request.Headers["Authorization"] = "Bearer " + accessToken;
}
await next.Invoke().ConfigureAwait(false);
});

running asp.net web api application in integration tests

I have a asp.net core web api application that runs great like this:
class Program
{
/// <summary>
/// Main method
/// </summary>
static void Main(string[] args)
{
// pass this as a parameter to specify what database I will like to use
Func<IServiceProvider, IMyDatabase> GetDatabaseFactory = provider =>
{
// used for testing purposes. In production I will use the real DB
return new MyDummyDatabase();
}
// create on a method so that it can be unit tested
WebApplication app = CreateMyAppWebApiApplication(GetDatabaseFactory);
// run application
app.Run();
} ​
}
And here is the method CreateMyAppWebApiApplication.
/* I removed a lot of stuff I just want to illustrate the idea. Moreover, I have hardocoded a lot of stuff for testing purposes. Once it works I will move it to a configuration file.*/
static WebApplication CreateMyAppWebApiApplication(StartAspDotNetParameters parameters)
{
​var builder = WebApplication.CreateBuilder();
​builder.WebHost.ConfigureKestrel(k =>
​{
​var port = 8888;
​k.Listen(System.Net.IPAddress.Any, port, listenOptions =>
​{
​// Enable support for HTTP1 and HTTP2 (required if you want to host gRPC endpoints)
​listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
​listenOptions.UseHttps();
​});
​});
​
​#region Add IoC dependencies
​// .. code some dependencies I need for the controlers
​#endregion
​// add controllers
​var mvcBuilder = builder.Services.AddControllers();
​// serialize enums as string
​mvcBuilder.AddJsonOptions(opts =>
​opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())
​);
​// Configure Swagger/OpenAPI. More info: https://aka.ms/aspnetcore/swashbuckle
​builder.Services.AddEndpointsApiExplorer();
​builder.Services.AddSwaggerGen(options =>
​{
​// code to configure...
​});
​WebApplication? app = builder.Build();
​#region Configure the HTTP request pipeline.
// first middleware to intercept swagger.json file
// I have hardocded the path for testing purposes
app.Use(async (HttpContext context, Func<Task> next) =>
{
if (requestUrl.EndsWith("myapp-swagger.json"))
{
var content = File.ReadAllText(#"T:\repos\.....\myapp-swagger.json.json");
context.Response.ContentLength = content.Length;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(content);
return;
}
else
{
// else execute next middleware
await next();
}
});
​
​// enable swagger
​app.UseSwagger();
​// change swager endpoint
​app.UseSwaggerUI(c =>
​{
​c.RoutePrefix = "documentation";
​c.SwaggerEndpoint("/myapp-swagger.json", "MY API");
​});
​app.UseHttpsRedirection();
​app.UseAuthorization();
​app.MapControllers();
​
​// This will run the application
​//// execute endpoint
​//app.Run();
​return app;
​#endregion
}
The things important to note about this method are:
​// I changed swagger default endpoint
app.UseSwaggerUI(c =>
{
​c.RoutePrefix = "documentation";
​c.SwaggerEndpoint("/myapp-swagger.json", "MY API");
});
// AND
// first middleware to intercept swagger.json file
// I have hardocded the path for testing purposes
app.Use(async (HttpContext context, Func<Task> next) =>
{
if (requestUrl.EndsWith("myapp-swagger.json"))
{
var content = File.ReadAllText(#"T:\repos\.....\myapp-swagger.json.json");
context.Response.ContentLength = content.Length;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(content);
return;
}
else
{
// else execute next middleware
await next();
}
});
Anyways that code works great.
Now here is the problem:
When I try to run that same code from a Tests project like this:
[Fact]
public async Task TestUserPermissions_IntegrationTest()
{
// pass the same dummyDatabase
WebApplication app = CreateMyAppWebApiApplication(provider =>
{
// used for testing purposes. In production I will use the real DB
return new MyDummyDatabase();
});
loginWorked = false;
var taskLogin = Task.Run(async () =>
{
// make sure app starts by waiting 5 seconds
await Task.Delay(5000);
using var client = new HttpClient();
var json = #"{ 'username':'tono', 'password':'myPassword'}".Replace("'", "\"");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result = await client.PostAsync("https://localhost:8888/api/LoginController/Login", content);
Console.WriteLine(result.StatusCode);
loginWorked = result.StatusCode == 200;
});
// run application
app.Run();
await taskLogin ;
Assert.True(loginWorked);
}
The app runs but I am not able to consume the API when running in the Test project
Finally found the answer. The controllers where not being found because I was running the project from a different assembly. This solution for stackoverflow made my Test pass:
https://stackoverflow.com/a/59121354/637142
In other words I ended up adding this code
// add controllers
var mvcBuilder = builder.Services.AddControllers();
// add controllers from this assembly. This is needed in case we are calling this method from unit tests project.
mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(typeof(MyCustomController).Assembly));

ChallengeAsync() from HttpContext does not redirect

We have a javascript app "wrapped" in an ASP Core MVC Application. Each AJAX-request from the javascript app hits a controller decorated with [Authorize].
In our startup method we've defined an AuhenticationScheme pointing toward our Identity Server. And then another scheme for the cookies that they are ultimately signed in as.
To ensure that all requests coming in are authenticated we use the following:
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
string auth = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer ", System.StringComparison.OrdinalIgnoreCase))
{
await context.ChallengeAsync("oidce", new AuthenticationProperties {
RedirectUri = "/"
});
}
else
{
await next();
}
}
else
{
await next();
}
});
Now if the cookie expires it triggers the "ChallengeAsync" - that doesn't really work if the call originated from an AJAX request made from the browser. I thought since I had the context here that it would simply override the AJAX and make the browser start the round-trip.
Is there a way to say to the browser that "no, this isn't an AJAX-response, go where I tell you to"?
As Tseng pointed out in the comments I implemented almost to the letter.
app.Use(async (context, next) =>
{
if (!context.User.Identity.IsAuthenticated)
{
if (context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
// webapp will then do a location.reload() which triggers the auth
context.Response.StatusCode = 401;
}
else
{
string auth = context.Request.Headers["Authorization"];
if (string.IsNullOrEmpty(auth) || !auth.StartsWith("Bearer ", System.StringComparison.OrdinalIgnoreCase))
{
await context.ChallengeAsync();
}
else
{
await next();
}
}
}
else
{
await next();
}
});
The javascript application then catches the Ajax exception and checks if the status is 401 and simply does window.location.reload()
Not an elegant solution, will probably rewrite in the future but it solves the immediate problem.

Asp.net Core Identity 2.0 Google Logout

I have started looking into Google signin and have added the normal provider as such.
ddGoogle(go =>
{
go.ClientId = "xxxxx";
go.ClientSecret = "-xxxxx";
go.SignInScheme = IdentityConstants.ExternalScheme;
});
My test method just to get it started looks like this
public ActionResult TestGoogle()
{
var redirectUrl = Url.Action(nameof(ExternalCallback), "Account", new { ReturnUrl = "" });
var properties = _signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return Challenge(properties, "Google");
}
All well and good I go to google Log in and get redirected with all required claims as expected.
The issue is when I call _signInManager.SignOutAsync() which does not seem to do anything. No errors, yet when I go back to my TestGoogle action I am redirected with all credentials to the callback.
Anything I am missing?
In your Logout Action add this code
return Redirect("https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=[[your_return_url_here]]");
This is how I configured my code:
Configure 2 Cookies, one (MainCookie) for local login and second (ExternalCookie) for google.
services.AddAuthentication("MainCookie").AddCookie("MainCookie", options =>
{
});
services.AddAuthentication("ExternalCookie").AddCookie("ExternalCookie", o =>
{
});
Configure google authentication as shown below:
services.AddAuthentication(
v =>
{
v.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
v.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).
AddGoogle("Google", googleOptions =>
{
googleOptions.ClientId = "xxx...";
googleOptions.ClientSecret = "zzz...";
googleOptions.SignInScheme = "ExternalCookie";
googleOptions.Events = new OAuthEvents
{
OnRedirectToAuthorizationEndpoint = context =>
{
context.Response.Redirect(context.RedirectUri + "&hd=" + System.Net.WebUtility.UrlEncode("gmail.com"));
return Task.CompletedTask;
}
};
});
TestGoogle() Method will redirect you to google login page.
You can then get the claims from google back like so:
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
var info = await HttpContext.AuthenticateAsync("ExternalCookie");
//Sign in to local cookie and logout of external cookie
await HttpContext.SignInAsync("MainCookie", info.Principal);
await HttpContext.SignOutAsync("ExternalCookie");
//ExternalCookie will be deleted at this point.
return RedirectToLocal(returnUrl);
}
If you want to now want to authenticate any method, you can do so as shown below:
[Authorize(AuthenticationSchemes = "MainCookie")]
public async Task<IActionResult> Contact()
{
//Only authenticated users are allowed.
}

How to validate JWT during websocket request. .net core

I am working on a small .net core app that uses JWT authentication and websockets.
I have succesfully implemented generating and validating tokens for standard web api controllers. However I also want to validate the token for a WebSocket request which of course won't work with the [Authorize] attribute.
I have setup my middleware pipeline like this:
app.UseWebSockets();
app.Use(async (http, next) => {
if (http.WebSockets.IsWebSocketRequest == false) {
await next();
return;
}
/// Handle websocket request here. How to check if token is valid?
});
// secretKey contains a secret passphrase only your server knows
var secretKey = .....;
var signKey = new SigningCredentials (
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey)),
SecurityAlgorithms.HmacSha256
);
var tokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = false,
ValidateAudience = false,
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signKey.Key,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.FromMinutes(1),
};
app.UseJwtBearerAuthentication(new JwtBearerOptions {
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
I hope this can help someone, even though the post is kind of old.
I found out the answer, not after Googling, but Binging ! I inspired myself from this official code.
You can write your own class that handles authorization very simply, using the magic of JwtBearerOptions. This class (hopefully) contains everything you need to validate the JWTs by yourself.
So, you have to inject it as a Service, as well as using it to configure your Authentication. Something like that in your Startup.ConfigureServices:
this.JwtOptions = new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = yourTokenValidationParameters
};
services.AddSingleton<JwtBearerOptions>(this.JwtOptions);
Then, you have to create a class that is gonna Validate your Token (This is where my code was inspired). Let's call it a Backer, because it's got your back !:
public class JwtBearerBacker
{
public JwtBearerOptions Options { get; private set; }
public JwtBearerBacker(JwtBearerOptions options)
{
this.Options = options;
}
public bool IsJwtValid(string token)
{
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, Options.TokenValidationParameters, out validatedToken);
}
catch (Exception ex)
{
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
validationFailures = new List<Exception>(1);
validationFailures.Add(ex);
continue;
}
return true;
}
}
return false;
}
}
Then, in your Middleware, just access the request header, the JwtOptions dependency and call the Backer:
protected string ObtainAppTokenFromHeader(string authHeader)
{
if (string.IsNullOrWhiteSpace(authHeader) || !authHeader.Contains(" "))
return null;
string[] authSchemeAndJwt = authHeader.Split(' ');
string authScheme = authSchemeAndJwt[0];
if (authScheme != "Bearer")
return null;
string jwt = authSchemeAndJwt[1];
return jwt;
}
protected async Task<bool> AuthorizeUserFromHttpContext(HttpContext context)
{
var jwtBearerOptions = context.RequestServices.GetRequiredService<JwtBearerOptions>() as JwtBearerOptions;
string jwt = this.ObtainAppTokenFromHeader(context.Request.Headers["Authorization"]);
if (jwt == null)
return false;
var jwtBacker = new JwtBearerBacker(jwtBearerOptions);
return jwtBacker.IsJwtValid(jwt);
}
public async Task Invoke(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
return;
if (!await this.AuthorizeUserFromHttpContext(context))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("The door is locked, dude. You're not authorized !");
return;
}
//... Whatever else you're doing in your middleware
}
Also, the AuthenticationTicket and any other information about the auth has been already handled by the framework's JwtBearerMiddleware, and will be returned anyway.
Finally, the client side. I advise you using a client library that actually supports additional HTTP Headers. For example, as far as I know, the W3C Javascript client does not provide this functionality.
Here you are! Thanks to Microsoft for its Open Source codebase.
I worked this out slightly different way as I was depending on WebSocket() on the client side.
So on the client side first I authenticate user to get token and attach it to the header as subprotocol:
socket = new WebSocket(connectionPath, ["client",token]);
Token is sent within request header under sec-websocket-protocol key.
So before authentication kicks in I extract token and append it to the context.
.AddJwtBearer(x =>
{
// ....
x.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Headers.ContainsKey("sec-websocket-protocol") && context.HttpContext.WebSockets.IsWebSocketRequest)
{
var token = context.Request.Headers["sec-websocket-protocol"].ToString();
// token arrives as string = "client, xxxxxxxxxxxxxxxxxxxxx"
context.Token = token.Substring(token.IndexOf(',') + 1).Trim();
context.Request.Headers["sec-websocket-protocol"] = "client";
}
return Task.CompletedTask;
}
};
Then on my WebSocket controller I simply stick [Authorize] attribute:
[Authorize]
[Route("api/[controller]")]
public class WSController : Controller
{
[HttpGet]
public async Task Get()
{
var context = ControllerContext.HttpContext;
WebSocket currentSocket = await context.WebSockets.AcceptWebSocketAsync("client"); // it's important to make sure the response returns the same subprotocol
// ...
}

Categories

Resources