how to authorize SignalR Hub in WebApi? - c#

I create a ASP.NET WebApi project, it use [Authorize] attribute to authorize controllers, such as:
[System.Web.Http.Authorize(Roles="Users, Admins")]
public ValueController : ApiController
{
[HttpGet]
public string GetValue()
{
return "Hello World !";
}
}
now I want to add Signalr to this project. This is what I did:
In App_Start/Startup.Auth.cs, add following codes in ConfigureAuth() function to start SignalR.
app.Map(signalr, map =
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
};
hubConfiguration.EnableDetailedErrors = true;
map.RunSignalR(hubConfiguration);
});
Then I add a MessageHub, trying to allow authorized user to send messages to all clients:
[Microsoft.AspNet.SignalR.Authorize]
public class MessageHub : Hub
{
public void SendMessage(string message)
{
Clients.All.SendMessage(message);
}
}
if I start this project, type http://localhost:4000/signalr/hubs in browser, I can receive a hubs js.
Q1 - For me that means SignalR is working on my WebApi project, right?
now I need to test SendMessage, first thing I did is login through http://localhost:4000/Token, then get an access_token. In webApi I usually put this token in Header:
Authorization : Bearer access_token
to access APIs.
Q2 - now what should I do with this token to access MessageHub?

Related

Attribute To Secure Web Api

I am working with a web api where it should have a request key and depending upon it, the api controller will do
specific task. I am using rest client program in vs code and did the following for testing:
GET http://localhost:PortNo/WeatherForecast/GetAllTeams
test: "12345678910" //Key
So in the controller, I did this to get the key value:
[HttpGet]
public async Task<ActionResult<IEnumerable<TeamDetails>>> GetAllTeams()
{
string Token = Request.Headers["test"]; //Getting the key value here
var teams = _service.GetAllTeams();
return Ok(teams)
}
But I've few things in mind and doing R & D like how can I make the above with an attribute. Say each controller
will have an attribute as follows and make the request invalid if no proper key found:
[InvalidToken] //This is the attribute
[HttpGet]
public async Task<ActionResult<IEnumerable<TeamDetails>>> GetAllTeams()
{
var teams = _service.GetAllTeams();
return Ok(teams)
}
I am not sure if this is going to make the api secure and my plan is to valid every http request (In my case, a simple form submission at the moment), so it should say the request is generated from the web api app.
N.B: I worked with web api earlier in small sections but now a broader thing to implement, so expecting few suggestions that can help me to guide for better design.
try it:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
..
public class InvalidToken : Attribute, IActionFilter
{
public InvalidToken( )
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
var Authorization = context.HttpContext.Request.Headers["test"];
if ( Authorization != "12345678910")
{
context.ModelState.AddModelError("Authorization", "Authorization failed!");
return;
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// "OnActionExecuted"
}
}
Startup.cs
services.AddScoped<InvalidToken>();
// add filter to whole api
services.AddControllers(options =>
{
options.Filters.Add<InvalidToken>();
});

How to secure endpoints on api resource based on client in identityserver

I have my own authorization server built on top identityserver4 where I want to secure all apis on a host. System is just a simple mimic of google developers or facebook developers where application owners sign up and get client id and client secrets for access grant on apis.
So I followed client_credentials flow on identityserver4 samples. All working fine. I built a public UI for app owners to create apps and choose which apis to access from their apps. I make use of IConfigurationDbContext for CRUD procecces on internal tables of identityserver.
The problem is I couldn't find a way to secure apis based on app owners' choices, when a developer crate an app and choose a few logical endpoints to access, they still can reach all enpoints. What I have done is as follows;
Authorization Server Startup
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryCaching()
.AddOperationalStore(storeOpitons =>
{
storeOpitons.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddConfigurationStore(storeOptions =>
{
storeOptions.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
});
Saving Client Method
public IActionResult SaveApp(ClientViewModel model, List<SelectedApi> selectedApis)
{
//ommited for brevity
Client client = new Client
{
Description = model.Description,
ClientName = model.Name,
RedirectUris = new[] { model.CallBackUri }
};
client.AllowedScopes = selectedApis.Where(a => a.apiValue == "true").Select(a => a.apiName).ToList();
//e.g : client.AllowedScopes = {"employee_api"};
_isRepository.SaveClient(client, userApp);
}
Api Project Startup
services.AddAuthentication("Bearer").AddJwtBearer(opt => {
opt.Authority = "http://localhost:5000";
opt.Audience = "employee_api";
opt.RequireHttpsMetadata = false;
});
Api Sample Controller
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
private readonly IEmployeeRepository _employeeRepoistory;
public EmployeeController(IServiceProvider provider, IEmployeeRepository employeeRepository) : base(provider)
{
_employeeRepoistory = employeeRepository;
}
[HttpGet]
public IActionResult GetEmployees([FromQuery] EmployeeResourceParameter parameter)
{
return Ok(_mapper.Map<IEnumerable<EmployeeModel>>(_employeeRepoistory.GetAll(parameter)));
}
[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
var emp = _employeeRepoistory.GetById(id);
return Ok(_mapper.Map<EmployeeModel>(emp));
}
}
What I want is if a developer choose employee_api, they should just reach to EmployeeController's endpoints. However right now, they can reach all the apis no matter of what their choices are.
What are the steps to take for this on api side or auth server side?
Finally I get it done.. First of all, I realized that it is important to grasp the relation between ApiResource -> Scopes, Clients -> AllowedScopes. I suggest you to read the parts about them in the docs and here
When a client is registered to identityserver and then choose the api endpoints (eg: organization, employee, calender) they should be registered as allowedScopes of client (they live in ClientScopes table), I was doing it in right way. What I was doing wrong is I suppose all these scopes are ApiResources (for my case, because all my apis are living in the same host which I call it as CommonServiceApi, just one web api app). I redefined my ApiResources and its Scopes, as follows;
new ApiResource("commonserviceapi", "Common Service API")
{
Scopes = {
new Scope("calender_api", "Calender Api"),
new Scope("employee_api", "Employee Api"),
new Scope("organization_api", "Organization Api"),
}
}
On the api side, endpoints should be authorized with policies as indicated here.
Within the access token, allowed scopes of the requesting client are passed to the api app, so api grants accesses according to these values.
So Api Startup
services.AddAuthentication("Bearer").AddJwtBearer(opt =>
{
opt.Authority = "http://localhost:5000";
opt.Audience = "commonserviceapi";
opt.RequireHttpsMetadata = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiEmployee", builder =>
{
builder.RequireScope("employee_api");
});
options.AddPolicy("ApiOrganization", builder =>
{
builder.RequireScope("organization_api");
});
});
And Api Controllers
[Authorize(Policy = "ApiEmployee")]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
...
RequireScope is an extension method of IdentityServer4.AccessTokenValidation package by the way. You should include this package to your api project.
And lastly, this was a confusing point for me; while requesting an access token from server, scope parameter should be empty, as identityserver takes it from client's allowdScopes values. Almost all samples were filling this field, so you'd think it should be filled.

OWIN-hosted web api: using windows authentication and allow anonymous access

I have a WebApi project self-hosted using OWIN.
I want to enable Windows Authentication on some of the controller's actions, but allow other actions to be called anonymously.
So, following some examples I found online, I setup my WebApi like this in my Statrup class:
public void Configuration(IAppBuilder appBuilder)
{
HttpListener listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication | AuthenticationSchemes.Anonymous; //Allow both WinAuth and anonymous auth
//setup routes and other stuff
//...
//Confirm configuration
appBuilder.UseWebApi(config);
}
Then, in my controller, I created two actions:
[HttpGet]
[Authorize]
public HttpResponseMessage ProtectedAction()
{
//do stuff...
}
[HttpGet]
[AllowAnonymous]
public HttpResponseMessage PublicAction()
{
//do stuff...
}
This, however, does not work.
Calling the action marked AllowAnonymous works as expected, but calling the one marked Authorize always returns a 401 error and the following message:
{
"Message": "Authorization has been denied for this request."
}
even if the caller supports windows authentication, tested on browsers (Chrome and Edge) and Postman.
What am I missing here?
Well, I found a workaround for this in another question.
Instead of specifying multiple auth modes (which doesn't work), you can chose the auth mode for each request at runtime, by setting up an AuthenticationSchemeSelector method like this:
public void Configuration(IAppBuilder app)
{
HttpListener listener = (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemeSelectorDelegate = new
AuthenticationSchemeSelector(GetAuthenticationScheme);
}
private AuthenticationSchemes GetAuthenticationScheme(HttpListenerRequest httpRequest)
{
if(/* some logic... */){
return AuthenticationSchemes.Anonymous;
}
else{
return AuthenticationSchemes.IntegratedWindowsAuthentication;
}
}
While not ideal (you have to manually check the request URL or some other parameter of the request to decide which method to use) it works.
Since your description about the question is bit limited I have set-up a demo app, where I implemented OAuthAuthorizationServerProvider as Provider for OAuthAuthorizationServerOptions and override GrantResourceOwnerCredentials and ValidateClientAuthentication
public void Configuration(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new ApplicationOAuthBearerAuthenticationProvider()
});
app.Use<AuthenticationResponseMiddleware>();
var options = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/xxxx"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new OwinAuthorisationProvider()
};
app.UseOAuthAuthorizationServer(options);
}
also tried to have a custom AuthorizeAttribute and added as filters in the configuration class .Filters.Add(new AuthorizeAttribute());
In AuthenticationResponseMiddleware i inherited OwinMiddleware and in the public override async Task Invoke(IOwinContext context) method please inspect the flow of the request.
It hits OAuthBearerAuthenticationProvider first in RequestToken method then to OwinMiddleware class, before going to any DelegatingHandler pipelines,
mostly your authentication is implemented in this layer.
Please comment your findings after this check, parallelly I too modify the API and update you, hope it can help you.

Signal R in Web Api

I have Web Api which serves to CRUD Posts from Web App, Android App and Desktop.
I want to add SignalR to the Web Api, every time when Action Create in Controller gets called I want to notify all users that Post is created.
Problem is, I can't find any implementation in only Web Api, all implementations are in Web App with Web Api or something like that. I read all MSDN documentation about it. I'm strugling for 3-4 days now.
I managed to get to the point where I implemented SignalR, and my server isn't created any signalr/hubs file that I need to call from Web App script. It's only created when I run app locally - if I publish it on Azure that file isn't created.
Anyone have concrete steps for Implementation only in Web Api?
I tried this blog, it has Web Api stuff but have js in project and local html added. It's not standalone REST api.
This is not about not creating signalr/hubs file. It's about creating standalone Web Api with SignalR.
I have startup:
public void Configuration(IAppBuilder app) {
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
app.MapSignalR("/signalr", new Microsoft.AspNet.SignalR.HubConfiguration());
}
My Hub:
public class ServiceStatusHub : Hub {
private static IHubContext hubContext =
GlobalHost.ConnectionManager.GetHubContext<ServiceStatusHub>();
public static void SetStatus(string message) {
hubContext.Clients.All.acknowledgeMessage(message);
}
}
And in my Api Controler I call:
ServiceStatusHub.SetStatus("Status changed!");
I made console application to test Api, added Signal R client and class:
class SignalRMasterClient {
public string Url { get; set; }
public HubConnection Connection { get; set; }
public IHubProxy Hub { get; set; }
public SignalRMasterClient(string url) {
Url = url;
Connection = new HubConnection(url, useDefaultUrl: false);
Hub = Connection.CreateHubProxy("ServiceStatusHub");
Connection.Start().Wait();
Hub.On<string>("acknowledgeMessage", (message) =>
{
Console.WriteLine("Message received: " + message);
/// TODO: Check status of the LDAP
/// and update status to Web API.
});
}
public void SayHello(string message) {
Hub.Invoke("hello", message);
Console.WriteLine("hello method is called!");
}
public void Stop() {
Connection.Stop();
}
}
program.cs:
var client = new SignalRMasterClient("myUrl.com/signalr");
// Send message to server.
client.SayHello("Message from client to Server!");
I getting 500 Internal Server Error.
How can I test is my Web Api signalR works for sure?
I see some problems:
1) You do not need the hubContext field in your hub. You inherit from Hub. This class contains allready a "Clients" property .
public class ServiceStatusHub : Hub {
public static void SetStatus(string message) {
Clients.All.acknowledgeMessage(message);
}
}
2) Log errors at starting of server.
public SignalRMasterClient(string url) {
Url = url;
Connection = new HubConnection(url, useDefaultUrl: false);
Hub = Connection.CreateHubProxy("ServiceStatusHub");
Connection.Start().ContinueWith(task => { if (task.IsFaulted) {
Console.WriteLine("There was an error opening the connection:{0}",
task.Exception.GetBaseException());
} else {
Console.WriteLine("Connected");
}
});
Hub.On<string>("acknowledgeMessage", (message) =>
{
Console.WriteLine("Message received: " + message);
/// TODO: Check status of the LDAP
/// and update status to Web API.
}).Wait();
}
3) Check Client url. You need no signalr there in your path at creation of your client.
just:
var client = new SignalRMasterClient("myUrl.com/");
Here you will find a running sample which does all you need:
SignalR Console app example

ASP.NET Web API 2 -> Windows Authentication -> Impersonate Windows user on Azure

We are developing an application with Windows Authentication that is used internally at a company. We have looked at ADFS but at the moment this is not an option. The problem is our test servers are entirely cloud based on Azure. I have been trying to find a way to activate a user but have not found a good solution.
My first idea was to turn off authentication completely. This works good but we have some resources that checks for user roles so I had to abandon that idea.
<system.web>
<authentication mode="None" />
</system.web>
Example method that returns 401 Unauthorized with authentication mode="None", obviously:
[Authorize(Roles = "Administrator")]
[HttpGet]
[Route("TestMethod")]
public IHttpActionResult TestMethod()
{
return Ok("It works!");
}
My second thought was to edit the WebApiConfig and try to add authentication headers in every request server side. However when I started looking at the NTLM Authentication Scheme for HTTP and the 4-way handshake I realized this would probably be impossible.
NTLM Authentication Scheme for HTTP
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Other code for WebAPI registerations here
config.MessageHandlers.Add(new AuthenticationHandler());
}
}
class AuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add authentication to every request...
return base.SendAsync(request, cancellationToken);
}
}
Since there is no Owin (Katana) I can not edit the standard App_Start -> Startup.Auth.cs -> public void ConfigureAuth(IAppBuilder app) and try something there. I don't know how I would build up the "user object" anyway.
Is there anything we can do about this or do we have to test everything locally? If we could impersonate one user to be logged in for every request this would be fine in the test environment.
In terms of faking the authentication and authorisation you should be able to set a generic user principal with the appropriate roles using a FilterAttribute.
public class TestIdentityFilter : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
filterContext.Principal = new GenericPrincipal(
new GenericIdentity(),
new string [] {"Administrator"});
}
}
You will need to set <authentication mode="None" /> as you did previously otherwise this code will never be hit in your test environment.
Adding this as a Global filter will override any other existing authentication system (for example if you deploy it to an authenticated environment by mistake). Obviously you will need to be very careful about only using this in your test system.
This example is based on MVC, I think there are some very small differences with WebApi but the basic principal applies.
Big thanks to #ste-fu for pointing me in the right direction. Complete code:
public class AppSettingsDynamicRolesAuthorizeAttribute : AuthorizeAttribute
{
public AppSettingsDynamicRolesAuthorizeAttribute(params string[] roleKeys)
{
List<string> roles = new List<string>(roleKeys.Length);
foreach (var roleKey in roleKeys)
{
roles.Add(WebConfigurationManager.AppSettings[roleKey]);
}
this.Roles = string.Join(",", roles);
}
public override void OnAuthorization(HttpActionContext filterContext)
{
if (Convert.ToBoolean(WebConfigurationManager.AppSettings["IsTestEnvironment"]))
{
filterContext.RequestContext.Principal = new GenericPrincipal(
new GenericIdentity("Spoofed-Oscar"),
new string[] { WebConfigurationManager.AppSettings[Role.Administrator] });
}
base.OnAuthorization(filterContext);
}
}
public static class Role
{
public const string Administrator = "Administrator";
public const string OtherRole = "OtherRole";
}
Can then be used like this:
[AppSettingsDynamicRolesAuthorize(Role.Administrator, Role.OtherRole)]
[HttpGet]
[Route("Test")]
public IHttpActionResult Get()
{
var userName = RequestContext.Principal.Identity.Name;
var user = HttpContext.Current.User.Identity;
return Ok("It works!");
}

Categories