For the purpose of downloading files I need to use a GET: /API/File/ID?bearerToken=XYZ... method.
I've created a DelegatingHandler to add my token to the AuthorizationHeader, but it appears the token validation may be done before this point...
All of the tokens at current at added by Angular adding the token to the HTTP header before the request.
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
GlobalFilters.Add(config);
app.UseWebApi(config);
config.MessageHandlers.Insert(0, new QueryStringBearerToken());
}
..
public class QueryStringBearerToken : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var bearerToken = request.GetQueryNameValuePairs().
Where(kvp => kvp.Key == "bearertoken")
.Select(kvp => kvp.Value)
.FirstOrDefault();
//QueryString exists and Header doesn't
if (!string.IsNullOrWhiteSpace(bearerToken) && !request.Headers.Any(x=>x.Key == "Authorization"))
{
request.Headers.Add("Authorization", "Bearer " + bearerToken);
}
return base.SendAsync(request, cancellationToken);
}
}
I presume you are using Katana's Bearer middleware? (judging by your call to ConfigureAuth?)
If so, the Katana middleware will indeed run before the Web API handlers and reject your request before it even gets a chance to be processed by the handler.
Instead of creating a handler you should move your functionality to Katana middleware.
Here's an example:
public class QueryBearerMiddleware : OwinMiddleware
{
public QueryBearerMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
string bearerToken = null;
if (context.Request.QueryString.HasValue)
{
var queryPairs = context.Request.QueryString.ToUriComponent()
.Substring(1)
.Split(new [] {'&'}, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Split('=')).ToDictionary(x => x[0], x => x[1]);
if (queryPairs.ContainsKey("bearertoken"))
{
bearerToken = queryPairs["bearertoken"];
}
}
//QueryString exists and Header doesn't
if (!string.IsNullOrWhiteSpace(bearerToken) && context.Request.Headers.All(x => x.Key != "Authorization"))
{
context.Request.Headers.Add("Authorization", new [] { "Bearer " + bearerToken });
}
await Next.Invoke(context);
}
}
You should register this middleware to run before the Bearer middlware.
Somewhere in your ConfigureAuth you should have a call to app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());. This new middleware we just created, should be registered before, i.e:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(QueryBearerMiddleware));
var config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
GlobalFilters.Add(config);
app.UseWebApi(config);
}
}
Related
I'm upgrading an old application in asp net mvc to a new version with asp core + angular 7. In the old app, we had an external service calling our api, and sending the auth token in the URL because it couldn't do otherwise.
I intercept it to inject the token in the header like this:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_PreSendRequestHeaders()
{
Response.Headers.Remove("X-Frame-Options");
Response.AddHeader("X-Frame-Options", "AllowAll");
}
private void Application_BeginRequest(object sender, EventArgs e)
{
var header = HttpContext.Current.Request;
var url = HttpContext.Current.Request.Url;
var Params = HttpContext.Current.Request.Params;
if (ReferenceEquals(null, HttpContext.Current.Request.Headers["Authorization"]))
{
var token = HttpContext.Current.Request.Params["access_token"];
if (!String.IsNullOrEmpty(token))
{
HttpContext.Current.Request.Headers.Add("Authorization", "Bearer " + token);
}
}
}
protected void Application_Start()
{
//DashboardConfig.RegisterService(RouteTable.Routes);
DevExtremeBundleConfig.RegisterBundles(BundleTable.Bundles);
C_Interface_Meta.IntialiserBdd();
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
//GlobalConfiguration.Configure(WebApiConfig.Register);
ASPxWebControl.CallbackError += Application_Error;
BundleConfig.RegisterBundles(BundleTable.Bundles);
DisableApplicationInsightsOnDebug();
}
/// <summary>
/// Disables the application insights locally.
/// </summary>
[Conditional("DEBUG")]
private static void DisableApplicationInsightsOnDebug()
{
TelemetryConfiguration.Active.DisableTelemetry = true;
}
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new PrettyPrintFilterAttribute());
}
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = HttpContext.Current.Server.GetLastError();
if (exception is HttpUnhandledException)
exception = exception.InnerException;
AddToLog(exception.Message, exception.StackTrace);
}
public static void AddToLog(string message, string stackTrace)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(DateTime.Now.ToLocalTime().ToString());
sb.AppendLine(message);
sb.AppendLine();
sb.AppendLine("Source File: " + HttpContext.Current.Request.RawUrl);
sb.AppendLine();
sb.AppendLine("Stack Trace: ");
sb.AppendLine(stackTrace);
for (int i = 0; i < 150; i++)
sb.Append("-");
sb.AppendLine();
HttpContext.Current.Application["Log"] += sb.ToString();
sb.AppendLine();
}
}
What is the equivalent way for doing that in an angular asp core application? After much searching, I didn't find anything.
Sounds like you're using JWT bearer and sending the token via query string. If that's the case, you could simply use an event handler OnMessageReceived to set the token dynamically :
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options=> {
options.TokenValidationParameters = new TokenValidationParameters{
// ValidIssuer, ValidAudience, IssuerSigningKey , ...
};
options.Events = new JwtBearerEvents() {
OnMessageReceived = async (context) =>{
// get bearer From Header/QueryString as you like
var bearer=context.HttpContext.Request.Query["access_token"].FirstOrDefault();
if(!String.IsNullOrEmpty(bearer)){
context.Token = bearer; // simply set the token
}
},
};
});
// other services ...
}
Or as suggested by Razgort, you could register a middleware to set the token.
Be careful for the order of middlewares.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// register it before other middlewares that rely on this token
app.Use(async(context,next)=>{
var bearer = context.Request.Headers["Authorization"].FirstOrDefault();
if(bearer==null){
bearer=context.Request.Query["access_token"].FirstOrDefault();
if(!String.IsNullOrEmpty(bearer)){
context.Request.Headers.Add("Authorization", "Bearer " + bearer);
}
}
await next();
});
// other middlewares ...
app.UseMvc(...)//
}
I think you are looking for angular interceptors, which pretty much doest the same thing. You want to inject the tokens something like this:
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
// Get the auth token from the service.
const authToken = this.auth.getAuthorizationToken();
// Clone the request and replace the original headers with
// cloned headers, updated with the authorization.
const authReq = req.clone({
headers: req.headers.set('Authorization', authToken)
});
// send cloned request with header to the next handler.
return next.handle(authReq);
}
}
Trying to follow the suggestions in the link below to pass a JWT token to my SignalR hub but so far it's not working. In particular, see David Fowler's suggestion on July 22, 2017. https://github.com/aspnet/SignalR/issues/130
My frontend is React so I'm simply adding the token to the querystring as follows where _token has my JWT token value:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/myhub?AUTHORIZATION=" + _token)
.configureLogging(signalR.LogLevel.Information)
.build();
In the ConfigureServices() method of my Startup.cs, I have the following configuration for Jwt tokens:
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if(context.HttpContext.WebSockets.IsWebSocketRequest)
context.Token = context.Request.Query["AUTHORIZATION"];
return Task.CompletedTask;
}
};
});
And this is what my Hub looks like:
[Authorize]
public class MyHub : Hub
{
private IBackendService _backendService;
public MyHub(IBackendService backendService)
{
_backendService = backendService;
}
public async Task SendMessage(string message)
{
// Regular SignalR stuff
// SignalR will now send the message to all connected users...
}
}
Basically, I'm getting the 401 Unauthorized error.
I put a break point where I check to see if the request is a web sockets request but I'm not hitting it. Looks like something in the pipeline is determining that the user is not authenticated.
What am I doing wrong in my code?
You can solve this by using custom middleware to handle grabbing the authentication token from the query string.
public class SignalRQueryStringAuthMiddleware
{
private readonly RequestDelegate _next;
public SignalRQueryStringAuthMiddleware(RequestDelegate next)
{
_next = next;
}
// Convert incomming qs auth token to a Authorization header so the rest of the chain
// can authorize the request correctly
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers["Connection"] == "Upgrade" &&
context.Request.Query.TryGetValue("authToken", out var token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token.First());
}
await _next.Invoke(context);
}
}
public static class SignalRQueryStringAuthExtensions
{
public static IApplicationBuilder UseSignalRQueryStringAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SignalRQueryStringAuthMiddleware>();
}
}
This will try to get the query string value "authToken" and it will set the heads so you can leverage your authentication middleware. You need to call this before the authentication middleware in the pipeline like so:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseSignalRQueryStringAuth();
app.UseAuthentication();
//...
}
EDIT
on a side note you should only append the token if the user is logged in:
if (accessToken) {
hubUrl += '?authToken' +'=' + accessToken;
}
this._hubConnection = new HubConnectionBuilder()
.withUrl(hubUrl)
.build();
I have also implemented this in my project. The shortest way of doing this is to add a middleware to your Configure method.
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);
});
It does the same thing as mentioned in the other answer. It adds the token to the header by reading it from the query string. Of course, you can separate out the implementation of custom middleware in a separate file.
In our ASP .NET Core 2.0, Web API, when the user logs in, we generate a GUID and return that to the user after storing it in database. What is the best practice to validate this token when the user submits a request to a controller having Authorize attribute on it.
Should I override AuthorizeAttribute.OnAuthorization and put my custom logic in there ? or is there any other place where I should place my custom logic ?
Thanks in advance.
In ASP .NET Core 2.0 you can write you own Middleware to validate token. You can see this video as exapmle - https://www.youtube.com/watch?v=n0llyujNGw8.
Summarily:
1. Create TokenMiddleware:
public class TokenMiddleware
{
// always should be RequestDelegate in constructor
private readonly RequestDelegate _next;
public TokenMiddleware(RequestDelegate next)
{
_next = next;
}
// always should be defiened Invoke or InvokeAsync with HttpContext and returned Task (You can also inject you services here - for example DataContext)
public async Task InvokeAsync(HttpContext context, DataContext dataContext)
{
var validKey = true;
// than you logic to validate token
if (!validKey)
{
context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
// if validm than next middleware Invoke
else
{
await _next.Invoke(context);
}
}
}
// Extension to IApplicationBuilder (to register you Middleware)
public static class TokenExtensions
{
public static IApplicationBuilder UseTokenAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TokenMiddleware>();
}
}
Registred you Middleware in Startup:
app.UseTokenAuth();
Question was made long time ago, but for people that might stumble upon it, here is the way I did it, taking advantage of authentication and authorization middlewares. The question doesn't have details about the way the token is passed in the request but I am assuming a standard Authorization header.
Create a custom AuthenticationHandler
MyCustomTokenHandler.cs
public class MyCustomTokenHandler: AuthenticationHandler<AuthenticationSchemeOptions>
{
public MyCustomTokenHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.NoResult();
}
if (!AuthenticationHeaderValue.TryParse(Request.Headers["Authorization"], out AuthenticationHeaderValue? headerValue))
{
return AuthenticateResult.NoResult();
}
if (!Scheme.Name.Equals(headerValue.Scheme, StringComparison.OrdinalIgnoreCase))
{
return AuthenticateResult.NoResult();
}
if (headerValue.Parameter == null)
{
return AuthenticateResult.NoResult();
}
//The token value is in headerValue.Parameter, call your db to verify it and get the user's data
var claims = new[] { new Claim(ClaimTypes.Name, "username found in db") };
//set more claims if you want
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Register the handler and enable authorization
Program.cs
builder.Services.AddAuthentication("Bearer").AddScheme<AuthenticationSchemeOptions, MyCustomTokenHandler>("Bearer", null);
//...
var app = builder. Build();
app.UseAuthentication();
app.UseAuthorization();
Most of the code is inspired by this blog post: https://joonasw.net/view/creating-auth-scheme-in-aspnet-core-2
In my Startup.Auth.cs:
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new PrincipalUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
My UserHub.cs:
public class UserHub : Hub
{
}
On the server-side, in one of my API Controller action (a Put related to a Grid Update):
[...]
var userHub = GlobalHost.ConnectionManager.GetHubContext<UserHub>();
// Line below does not work
// userHub.Clients.User(userId).send("Hi");
// But this line below works when sending the message to everybody
userHub.Clients.All.send("Hi");
return Request.CreateResponse(HttpStatusCode.OK);
On the JS View client-side:
#Request.IsAuthenticated
{
<script>
$(function() {
var userHub = $.connection.userHub;
console.log(userHub.client);
userHub.client.send = function(message) {
alert('received: ' + message);
};
$.connection.hub.start().done(function() {
});
});
</script>
}
Why when passing the userId my client receives nothing?
(also tried passing the userName, with the same outcome).
[EDIT]
Technically the right way to achieve that is to leverage the implementation of the IUserIdProvider:
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections#IUserIdProvider
SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*
However, I've noticed that in my case the User property of the IRequest object passed to the GetUserId method is always set to null...
The solution was actually already given for another issue, right here: https://stackoverflow.com/a/22028296/4636721
The problem was all about the initialization order in the Startup.Auth.cs:
SignalR must be initialized after the cookies and the OwinContext initialization, such as that IUserIdProvider passed to GlobalHost.DependencyResolver.Register receives a IRequest containing a non-null User for its GetUserId method:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder appBuilder)
{
// Order matters here...
// Otherwise SignalR won't get Identity User information passed to Id Provider...
ConfigOwinContext(appBuilder);
ConfigCookies(appBuilder);
ConfigSignalR(appBuilder);
}
private static void ConfigOwinContext(IAppBuilder appBuilder)
{
appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext(LdapAdEmailAuthenticator.Create);
}
private static void ConfigCookies(IAppBuilder appBuilder)
{
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>
(
TimeSpan.FromHours(4),
(manager, user) => user.GenerateUserIdentityAsync(manager)
)
}
});
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
appBuilder.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new HubIdentityUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
}
Using the IUserIdProvider below, I explicit declared that I want to use the UserId and not the UserName as given by the default implementation of the IUserIdProvider, aka PrincipalUserIdProvider:
public class HubIdentityUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
return request == null
? throw new ArgumentNullException(nameof(request))
: request.User?.Identity?.GetUserId();
}
}
I am trying to use the new User Id provider specified in signalr 2 to send messages to a specific user. When I call the Clients.All method, I see this working as my javascript code gets called from the server and the ui produces some expected text for my test case. However, when I switch to Clients.User the client side code is never called from the server. I followed the code outlined in this example: SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*.
NotificationHub.cs:
public class NotificationHub : Hub
{
[Authorize]
public void NotifyUser(string userId, int message)
{
Clients.User(userId).DispatchMessage(message);
}
public override Task OnConnected()
{
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
}
IUserIdProvider.cs:
public class UserIdProvider : IUserIdProvider
{
MemberService _memberService;
public UserIdProvider()
{
}
public string GetUserId(IRequest request)
{
long UserId = 0;
if (request.User != null && request.User.Identity != null &&
request.User.Identity.Name != null)
{
var currenUser = Task.Run(() => _memberService.FindByUserName(request.User.Identity.Name)).Result;
UserId = currenUser.UserId;
}
return UserId.ToString();
}
}
Startup.cs
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute(
"Default2",
"api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute(
"DefaultApi2",
"api/{controller}/{id}",
new { id = RouteParameter.Optional });
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var idProvider = new UserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerAuthenticationProvider()
});
var hubConfiguration = new HubConfiguration
{
};
map.RunSignalR(hubConfiguration);
});
app.MapSignalR();
QuerstringOAuthBearerAuthenticationProvider:
public class QueryStringOAuthBearerAuthenticationProvider
: OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context == null) throw new ArgumentNullException("context");
// try to find bearer token in a cookie
// (by default OAuthBearerAuthenticationHandler
// only checks Authorization header)
var tokenCookie = context.OwinContext.Request.Cookies["BearerToken"];
if (!string.IsNullOrEmpty(tokenCookie))
context.Token = tokenCookie;
return Task.FromResult<object>(null);
}
}
Do I need to map the user to the connections myself using the IUserIdProvider through the OnConnected, OnDisconnected, etc. or does this happen automatically behind the scenes? Is there someone wrong in my posted code that could be a problem as well? I am running signalr from the same environment as my web api rest services, don't know if this makes a difference and using the default bearer token setup web api is using.
It would be far easier for you to create a group based on the connectionid of the connecting client, in the onConnected event and broadcast to the group that matches the connected id, that way if the client disconnects, when they reconnect they would simply belong to a new group the themselves. Unless of course you are required to have an authenticated user.