I have the following constellation:
I am using a asp.net core web api project which also includes a HubContext. The first thing a user has to do is to make an api call to my UsersController : BaseController. Here he/she calls the api/login route passing the according credentials. The return value of the Login() function is a JwtBearerToken, which the user from then on uses for all other api calls.
Once the token has been issued, the user (Client) establishes a SignalR connection over my ConnectionHub : Hub.
So far everything works well, the user gets authenticated using the token when calling api methods and I also can track the according Session state inside the scope of my ConnectionHub.
Now I have to retrieve the users (SignalR) session id whenever he/she makes an api call.
i. e. When the user calls a method in my UsersController I want to do something like this:
[HttpGet]
public ActionResult<List<User>> GetAll()
{
// Here I want to retrieve the (SignalR) session id of the user calling this method.
return Ok( userRepository.GetAllUsers() );
}
So far the only idea I have is to make the user send his SignalR-SessionId with the according api call, but what I'd like to achieve is to read the Id on the server side. How can I achieve this?
According to the Microsoft documentation, it is not possible to get the user's connectionId outside the hub (e.g. in a controller):
When hub methods are called from outside of the Hub class, there's no caller associated with the invocation. Therefore, there's no access to the ConnectionId, Caller, and Others properties.
However, you can get the users with JavaScript by calling a hub method. Here you have access to the connectionId and to the database using your repository (make sure it is available via Dependency Injection).
I don't know what you want to do with the user's exactly, but you can simply return the users in the hub method and do something with the connectionId.
YourHubClass.cs
public Task GetAllUsers()
{
// Get the ConnectionId
var connectionId = Context.ConnectionId;
// Get the users list
var users = userRepository.GetAllUsers();
// ...
return Clients.User(user).SendAsync("UserListRequested", users);
}
Related
I have to implement authorization for my web api using another/external API. So I have to get the JWT from the request and call another API with that token to know whether the user is authorized.
presently my authentication is working, and I am using
IServiceCollection.AddAuthentication().AddJwtBearer() // removed code to set options
in sample above, I have removed code to provide options and setting the TokenValidationParameters. So my auth logic is working as expected.
Now i am looking to implement custom Authorization. In my custom authorization logic i have to make call to another/external API and pass some parameters. The parameters will be different for different action methods in my controller. The external API will return bool (indicating whether authorized or not), I don't have need to maintain any application role/claims in my code.
is using dynamic policy name and string parsing as mentioned in doc the only/recommended option.
So i have to get jwttoken from request and call another API with that token to know if user is authorized or not.
You should try to prevent having to make an an outbound API request for each request your API gets.
It seems like you have an external authentication service which lets your users log in and returns a token of sorts. You need to know how that third party signs their tokens, then obtain some form of (public) key from them.
With this key you can validate whether the token has been signed by the party you trust. For this, you configure the appropriate TokenValidationParameters which you pass to your services.AddAuthentication().AddJwtBearer() so you can let Identity validate their token using their keys.
See:
Authorize with a specific scheme in ASP.NET Core
Microsoft.AspNetCore.Authentication.JwtBearer Namespace
Ultimately you'd also configure some sort of background job that cycles the keys of the external provider when they do, if they do, which they hopefully do.
As for your updated question: you appear to want to use an external service for authorization, i.e. who can do what.
You'll have to implement this yourself. Usually this is done using scopes, which are retrieved once, during authentication. These can contain values like "finance" to give access to financial controllers, or "finance:orders:list finance:products".
[RequiredScope("finance:orders", "finance:orders:list")]
public IActionResult Index()
{
return View();
}
If the API you're talking to does not have a way to retrieve the relevant scopes, claims or permissions during authentication (or once per resource), then you can't, for example, cache the user's roles to your controllers or entities.
You need to realise this will incur extra overhead per API call, as well as your application being down when the authentication/authorization service is down.
If you still want to do this, the most trivial way to do async authorization on a controller would be a policy:
public class AuthorizeWithAuthServiceRequirement : IAuthorizationRequirement
{
public const string PolicyName = "external-authorization-service";
}
public class AuthorizeWithAuthServiceHandler : AuthorizationHandler<AuthorizeWithAuthServiceRequirement>
{
private IYourApiService _yourApiService;
public AuthorizeWithAuthServiceHandler(IYourApiService yourApiService/* your DI here */)
{
_yourApiService = yourApiService;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeWithAuthServiceRequirement requirement)
{
var httpContext = context.Resource as HttpContext
?? throw new ArgumentException("Can't obtain HttpContext");
// Get the user or their claims or the ID from the route or something
var user = httpContext.User;
var claim = user.FindAll("foo-claim");
var allClaims = user.Claims;
var id = httpContext.Request.RouteValues["id"];
// TODO: response and error handling
var isUserAuthorized = _yourApiService.IsUserAuthorized(user, id, entity, claim, ...);
if (!isUserAuthorized)
{
context.Fail(/* TODO: reason */);
}
}
}
You register this with DI like this:
// Register the handler for dependency injection
services.AddSingleton<IAuthorizationHandler, AuthorizeWithAuthServiceHandler>();
// Register the policy
services.AddAuthorization(options =>
{
options.AddPolicy(AuthorizeWithAuthServiceRequirement.PolicyName, x => { x.AddRequirements(new AuthorizeWithAuthServiceRequirement()); });
});
And then apply it to a controller or action method:
[Authorize(Policy = AuthorizeWithAuthServiceRequirement.PolicyName)]
public class FooController : Controller
{
}
If you want more fine-grained control like custom attributes with parameters (like [CustomAuthorization(ApiPermission.Foo)]) per controller or action, or if you want to first load an entity and pass that to the handler, see Ilja in Asp.Net Core: Access custom AuthorizeAttribute property in AuthorizeHandler and their GitHub repository demonstrating three different approaches.
In my manage cars controller I have an action method
ParkCar(licenseplate, latitude, longitude)
that is called after I press a button on frontend. When I press that button, an ajax request is sent with my current location and the selected car that I want to park.
When the action starts running I want to check some info about the location (ex: if there are to many cars nearby) that was sent and send and display a form on frontend with the message :
Are you sure you want to park here?
After the question was answered on frontend I want to catch that answer in my controller's action and resume it and park the car if the user wants to or to send a failed attempt if the users doesn't.
I want to obtain something like this:
public IActionResult ParkCar(string licenseplate,double lat,double lng)
{
var car = _carsService.GetCarByLicensePlate(licenseplate);
// Check how many cars are nearby (1km)
// If there are too many cars... Send a message to frotend: "Are you sure you want to park here?
// get the message from frontend
// if the message sent by user is YES
_carsService.ParkCar(car, lat, lng);
_carsService.FindAndAssignCityToCar(car);
return Ok("Car parked successfully!");
// else return failed
}
Edit: I forgot to mention that I want this strictly with SignalR.
If you'd like to implement real-time client-to-server and server-to-client communications with SignalR in your application, you can refer to this doc to create a hub and hub methods based on your actual requirement and scenario.
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-5.0#create-and-use-hubs
then you can implement SignalR JavaScript client code in your corresponding view page(s), and connect to your hub server and invoke the hub method(s) in your code logic.
https://learn.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-5.0
How can I ask for something from frontend in controller using SignalR and ASP.NET Core MVC
Based on the code of your ParkCar action method, we can find that your call _carsService methods to do business logic, so you can inject same service in your hub method and perform same code logic in your hub method(s).
This is a follow-up to another question and answer. What's the effect of calling HubContext.Clients.Caller or HubContext.Clients.Others from the controller? I see it depends on the connection ID. What value would it have in this situation?
If the connection ID (and thus Caller and Others) is invalid then (from within the controller action) how could I obtain a connection ID (for the client currently calling the Web API) that I could use with HubContext.Clients's methods?
What's the effect of calling HubContext.Clients.Caller or HubContext.Clients.Others from the controller? I see it depends on the connection ID. What value would it have in this situation?
There is neither .Caller nor .Others on HubContext.Clients (of type HubClients<THub>).
"It's not possible to access those from IHubContext. Caller and Others both depend on knowing which client is the caller. When you're in IHubContext, we don't know and there may not even be a current caller since you could be in an MVC controller action, etc."
— aspnet/SignalR#2274 (comment)
(from within the controller action) how could I obtain a connection ID (for the client currently calling the Web API) that I could use with HubContext.Clients's methods?
"there is no way of knowing who the current caller is from the IHubContext"
— aspnet/SignalR#2274 (comment)
Using Groups and Users may be a solution
"If you have access to the User ID of the user who initiated the action, you can use .Users(userId) to send a message to all of that user's connections. Similarly, you add the SignalR connections to a group and send to that group using .Group(groupName)"
— aspnet/SignalR#2274 (comment)
Pass the connection ID from the client side
As of #microsoft/signalr 3.0.0, connectionId is available on HubConnection:
controller.doStuff(hubConnection.connectionId);
Outdated answer written at the time of #aspnet/signalr 1.0.0-rc1-update1:
You can get the connection ID on the client that calls the API, and then send it to the controller.
Hub:
public string GetConnectionId()
{
return Context.ConnectionId;
}
Client:
connection.invoke('getConnectionId')
.then(function (connectionId) {
// Send the connectionId to controller
controller.doStuff(connectionId);
});
I am trying to implement authentication for my web api.
I have read about different techniques of api authentication and the token technique is the most reasonable for me.
I read different articles about how to implement token based authentication in asp.net but they all rely on different libraries such as OAuth or Owin which also provide their own method of database interactions.
The thing is that I have already implemented database interaction with abstract repositories and entities and I would like to find out how can I implement api authentication easily and simply without interfering with my current design.
(By the way, my project is built on top of an empty web api project, so it doesn't come with all the bootstrap and authentication classes).
Thank you
One solution I've seen is to use .NET's HttpApplicationState class and store tokens in appstate; this way you're not directly messing with Session (which would be a REST antipattern), but you can still track all currently logged in users and use HttpContext/HttpActionContext to x-ref active tokens in the app. The benefit to using HttpActionContext is that it is thread-safe, whereas HttpContext is not, so you can lock the appstate, mess with the HttpContext of an individual request, and then unlock the appstate to allow other threads in.
Since locking/unlocking appstate does tie up the app, I'm not sure how well this solution scales, but here it is anyway . . .
General outline:
When a user first logs in, a token is generated for him/her and stored in appstate. Then you can tag any API calls that require authentication (or that need other information stored on that user) with a custom attribute that checks for that token in the appstate, sending the token name as a header in the API call (e.g. "{token-name: TOKEN}").
Here's a brief example:
[in Controller method first activated at login:]
CustomUserObject user = new CustomUserObject();
//store user props
string token = Guid.NewGuid().ToString();
//create AppState instance, mine's called _appState
//...
_appState.Lock();
_appState[token] = user;
_appState.UnLock();
//...
[Then in global.asax:]
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
string token = string.Empty;
if (request.Headers.GetValues("token-name") != null)
{
token = request.Headers.GetValues("token-name").FirstOrDefault().ToString();
IAppStateService appService; //<--- I've created a custom service tier class for appstate stuff
//Get appState instance, however makes sense for you.
//I'm using repo pattern with UnitOfWork, so mine looks like this...
//"IContainer ioc = DependencyResolution.IoC.Initialize();"
//"IAppStateService appService = ioc.GetInstance<IAppStateService>();"
appService.SetHttpApplicationState(HttpContext.Current.Application);
bool isAuthorized = appService.CheckTokenAndDoStuff(token);
//inside that method ^^^ you'll do stuff like
//"_appState.Lock();"
//"if (_appState[token] == null) return false" (or whatever)
//"_appState.Unlock();"
}
if (isAuthorized)
{
HttpResponseMessage resp = request.CreateResponse(HttpStatusCode.OK);
resp.Headers.Add("AuthenticationToken", token);
resp.Headers.Add("WWW-Authenticate", "Basic");
resp.Headers.Add("AuthenticationStatus", "Authorized");
}
return isAuthorized;
}
[then in webapi]
[HttpPost]
[CustomAuthorize]
public HttpResponseMessage NameOfMethod(...)...
...and that should x-check your appstate for your user token for you. Just make sure to include your token in your request header, and make sure to include the Basic Auth info in your response header.
I have a SignalR Hub with a non-static method that adds creates a new group based on the email address entered in a form:
public class EmailHub : Hub
{
public void AddEmail(string email)
{
base.Groups.Add(base.Context.ConnectionId, email);
}
}
I would like to call this Hub method from my MVC controller. My method currently looks something like this:
public class MyController : Controller
{
public ActionResult AddEmail(string email)
{
var hub = GlobalHost.ConnectionManager.GetHubContext<EmailHub>();
hub.Clients.All.AddEmail(email);
return View();
}
}
However, the code in the controller does not call the hub method. What can I change to be able to invoke the hub method successfully?
You'd have to pass your ConnectionId as a parameter, and you can't get that until SignalR is already connected.
SignalR connections are only present for one "page view" on the client. In other words, if I go to /chat/rooms/1, I get a ConnectionId, then if I navigate to /chat/rooms/2, I get a different ConnectionId. Because of that, base.Context.ConnectionId essentially doesn't exist when you're trying to use it here.
That leaves you with two options.
Subscribe to updates after SignalR connects on each page. In this scenario, you'd file a typical AddEmail request, then in JavaScript after that View() loads, you load SignalR, connect, then file a hub.server.addEmail(email). This is a standard approach in SignalR.
This is essentially the same thing, but if you were using an SPA framework that lets you persist your SignalR connection between views, that would work. Of course, that's a pretty significant change.
I've based all of this on the assumption that your action AddEmail is actually a page, which I inferred from that it returns a ViewResult. If that's called with AJAX, you could just append the ConnectionId as a query parameter and all of this would be moot.