How to use JwtBearer to authenticate SignalR client? - c#

I have project to use SignalR from desktop application. So my application have 2 method authentication. the one is cookie for web, the one is jwt for signalr client. cause the client comes from desktop.
How to exactly authentication, i check header request, client sent
Authorization: Bearer testing
in server, i assigns the token to MessageReceivedContext.Token. so i think, it automatically handle by some magic in background by AuthorizeAttribute. the description of property Bearer Token. This will give the application an opportunity to retrieve a token from an alternative location.
Client.js
"use strict";
var connection = new signalR.HubConnectionBuilder()
.withUrl("/socket", {
accessTokenFactory: () => "testing"
})
.build();
connection.start();
Hub.cs
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class SocketHub : Hub
{
public override Task OnConnectedAsync()
{
return Task.Run(()
=> Console.WriteLine(Context.ConnectionId));
}
}
Program.cs
AddJwtBearer(options =>
{
options.Authority = "http://localhost:5000/socket";
options.RequireHttpsMetadata = false;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Headers.Authorization;
// If the request is for our hub...
if (!string.IsNullOrEmpty(accessToken))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});

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);
});

OAuth2 Authentication failed of .net core api call fails using Msal.js on a React-Typescript web app

I have a client Typescript React web application that needs to communicate with a .net core api. On client the msal.js was integrated. I am able to login in AAD, get the user account and the access token and pass it as a Bearer to the api call. However the authentication on api level fails.
On Client:
config: Msal.Configuration = buildConfiguration({ auth: {
clientId: 'clientId',
authority: 'authority',
validateAuthority: true,
navigateToLoginRequestUrl: false
} });
request = {
scopes: ["user.read"]
};
userAgentApp : Msal.UserAgentApplication = new Msal.UserAgentApplication(this.config);
private _accessToken: string = '';
public async GetAccessToken(): Promise<string>{
if(this._accessToken == '')
{
await this.userAgentApp.acquireTokenSilent(this.request)
.then((tokenResponse) => this._accessToken = tokenResponse.accessToken)
.catch((error) => console.log(error))
}
return this._accessToken;
}
public login() {
console.log("before login");
this.userAgentApp.handleRedirectCallback((error, response) =>
{
console.error(error)
});
this.userAgentApp.loginRedirect(this.request);
console.log("after login")
}
// Calling the api
axios.defaults.headers.common = {'Authorization': `Bearer ${accessToken}`}
const travelExpensesResponse = await axios.get("https://localhost:44350/api/reisekostenantrag")
On Api
// Start.cs ConfigureServices
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Audience = "clientId";
options.Authority = "authority";
});
I am gettting a SecurityTokenInvalidSignatureException: IDX10511: Signature validation failed.
Where could be the problem?
Are there any samples to show how can I pass a token to .net core api from a client app?
The problem was that the token was given this scope user.read which is a Graph API scope.
So the solution was to register the server app in azure and create a custom scope.
Then on client app:
request = {
scopes: ["api://clientid/access_as_user"]
};

Razor Page Authorization stuck in loops with external cookie

I have an ASP.NET Core app that I am integrating with Auth0. After the authentication, I want to redirect to a page to collect information to create a local account, just like the default Facebook and Google extensions do.
I set up a main cookie, an external cookie and my Auth0 point. It then does a callback to the page (/Account/ExternalLogin), where I sign in to the main cookie after doing whatever they need to do, and redirect to a page that requires authorization (/Profile. This all works fine.
However, if I just try to go to that page rather than via the Login route, i get stuck in a loop.
I'm quite sure I'm missing just one stupid thing, but can't seem to get it.
I've tried to pretty much every combination of things I can figure out and have hit the wall. I'm sure it's something stupid.
Here's my relevant part of startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add authentication services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "MainCookie";
options.DefaultChallengeScheme = "Auth0";
})
.AddCookie("MainCookie", options =>
{
options.ForwardChallenge = "Auth0";
})
.AddCookie("External", options =>
{
})
.AddOpenIdConnect("Auth0", options =>
{
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Domain"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SignInScheme = "External";
// Set the callback path, so Auth0 will call back to http://localhost:3000/callback
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
options.Events = new OpenIdConnectEvents
{
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = $"{request.Scheme}://{request.Host}{request.PathBase}{postLogoutUri}";
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri) }";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Profile");
});
}
Here's AccountController
public class AccountController : Controller
{
public async Task Login(string returnUrl = "/")
{
var redirectUrl = Url.Page("/ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = redirectUrl });
}
[Authorize]
public async Task Logout()
{
await HttpContext.SignOutAsync("External");
await HttpContext.SignOutAsync("MainCookie");
await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties
{
RedirectUri = Url.Action("Index", "Home")
});
}
}
SO we redirect to ExternalLogin callback. Currently there is just a submit button that goes to the Confirm callback which completes the login. This will eventually be replaced with a check to see if I have an account for them, and force them to register.
public class ExternalLoginModel : PageModel
{
public IActionResult OnPost(string provider, string returnUrl = null)
{
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
return new ChallengeResult(provider, null);
}
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
return Page();
}
public async Task<IActionResult> OnPostConfirmAsync()
{
var claimsPrincipal = await HttpContext.AuthenticateAsync("External");
await HttpContext.SignInAsync("MainCookie", claimsPrincipal.Principal);
await HttpContext.SignOutAsync("External");
return RedirectToPage("/Profile");
}
}
So when I go /Account/Login, it correctly sends me to Auth0, then to ExternalLogin, and I can click the button and set the Main Cookie. This then lets me access /Profile.
However, If I'm not already authorized, If I execute /Profile, I then kick over to Auth0, but after authenticating I just get stuck in a loop like this.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 GET https://localhost:44375/profile
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'Page: /Profile'
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Route matched with {page = "/Profile", action = "", controller = ""}. Executing page /Profile
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes ().
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:Information: AuthenticationScheme: Auth0 was challenged.
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker:Information: Executed page /Profile in 11.2594ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'Page: /Profile'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 28.548ms 302
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 POST https://localhost:44375/callback application/x-www-form-urlencoded 375
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: External signed in.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 113.1223ms 302
Changing options.DefaultChallengeScheme = "Auth0" to options.DefaultChallengeScheme = "MainCookie" was all that was needed.

Append Authorization Header Before Authorizing Web Sockets

I'm in the process of refactoring my API to use the built in .Net authentication instead of IdentityServer4
In my old code I would append the authentication token to the websocket address and inject a header using the middleware
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.Query.TryGetValue("A5S0kT0k", out var token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token.First());
}
await _next.Invoke(context);
}
}
I can see that my middleware is being executed as expected an appending the proper authorization Header.
However in my startup my Authorization never seems to be called and it moves directly to connecting to the websocket'
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(cfg => {
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.Events = new JwtBearerEvents
{
OnMessageReceived = async (ctx) =>
{
Console.WriteLine(ctx.Token);
},
OnTokenValidated = async (ctx) =>
{
Console.WriteLine("BreakPoint");
},
OnAuthenticationFailed = async (ctx) =>
{
Console.WriteLine("Breakpoint");
}
};
cfg.TokenValidationParameters = tokenValidationParameters;
});
Here is the order of execution of my pipeline in the configure
app.UseSignalRQueryStringAuth();
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<DefaultServiceHubBase<MessageDTO>>("/messages");
routes.MapHub<DefaultServiceHubBase<ConversationDTO>>("/conversations");
routes.MapHub<InMemoryHub<UserLocationDTO>>("/user-locations");
});
I configure my pipeline so that the middleware is hit first but the authentication I can never hit any of my breakpoint in the JWTBearer section, However if I make a standard HttpRequest everything works fine?
My OnMessageReceived is ignored and it goes directly to the onconnect function in my hub why is this happening?
Not sure why but it turns out I need to added a default challenge schema as well
services.AddAuthentication(options =>
{
// Identity made Cookie authentication the default.
// However, we want JWT Bearer Auth to be the default.
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
I found this in the Signalr Authentication Docs

Asp.net Core Authorize user with Policy

I am trying to authorize an user with a bearer token send from the request header.
I added this code in startup file of resource server.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
Here is my method in controller.
[Authorize("Bearer")]
[HttpGet]
[Route("list")]
public IEnumerable<Products> List()
{
string Authorization = Request.Headers["Authorization"];
}
Application showing me error 401 Unauthorized even if i had token
I am sending this Token in the header request
Authorization:Bearer "xyz"
To work with Bearer token you have to add the following code on your Configure method
public void Configure(IApplicationBuilder app) {
app.UseJwtBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.Audience = "OAuth:Audience";
options.Authority = "OAuth:Authority";
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress: options.Authority + ".well-known/openid-configuration",
configRetriever: new OpenIdConnectConfigurationRetriever(),
docRetriever: new HttpDocumentRetriever() { RequireHttps = false });
});
}
You will also need a middleware to handle the authorization process.
Have a look at AspNet.Security.OpenIdConnect.Server and OpenIddict

Categories

Resources