I've got a web application created with ASP.NET and a windows native client program written in c#.
The windows native program needs to send and fetch data from the ASP.NET web application.
I guess in the web application I'll need a controller for the external calls. And in the client Software I somehow Need to call them.
Is there a way to achieve calls with complex data types (lists of classes) as parameters?
How do I secure the calls from the client? Simple http-logon?
for example I'd like to transfer an instance of this class to or from the ASP.NET web application:
public class Address
{
public String Street {get;set;}
public String City {get;set;}
}
public class CustomerInformation
{
public String No {get;set;}
public String Name {get;set;}
public List<Address> Addresses {get;set;}
}
Of course the Windows client is running somewhere local while the ASP.NET Service is running in the web.
I would add API controller and put some methods there. For instance
// Addresses API
public class AddressController : ApiController
{
private readonly IRepository<Address> _repository;
public AddressController(IRepository<Address> repository)
{
_repository = repository;
}
[BasicAuthorize]
public IList<Address> GetList()
{
return _repository.GetAll();
}
}
// Constomer information API
public class CustomerInformationController : ApiController
{
private readonly IRepository<CustomerInformation> _repository;
public CustomerInformationController(IRepository<CustomerInformation> repository)
{
_repository = repository;
}
[BasicAuthorize]
public IList<CustomerInformation> GetList()
{
return _repository.GetAll();
}
}
To secure those methods you can use Basic authentication. This means that you can add authorization header for each request:
For example how it looks for user "myuser" with password "test"
Authorization: basic bXl1c2VyOnRlc3Q=
// Custom attribute for Basic authentication
public class BasicAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
private readonly string[] _permissionNames;
public BasicAuthorizeAttribute()
{
}
public BasicAuthorizeAttribute(params string[] permissionNames)
{
_permissionNames = permissionNames;
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
// check if user has been already authorized
if (base.IsAuthorized(actionContext))
return true;
var user = AuthenticateUser(actionContext);
// here you can check roles and permissions
return user != null;
}
private IUser AuthenticateUser(HttpActionContext context)
{
var request = context.Request;
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader != null)
{
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeader.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && authHeader.Parameter != null)
return AuthenticateUser(authHeader.Parameter);
}
return null;
}
private IUser AuthenticateUser(string credentials)
{
try
{
// parse values
var encoding = Encoding.GetEncoding("iso-8859-1");
credentials = encoding.GetString(Convert.FromBase64String(credentials));
var credentialsArray = credentials.Split(':');
var username = credentialsArray[0];
var password = credentialsArray[1];
// authentication
var membershipService = new IMembershipService();
return membershipService.ValidateUser(username, password);
}
catch (Exception)
{
// Credentials were not formatted correctly.
return null;
}
}
}
On client side you can use HttpClient to send async request
public async Task<Address[]> GetAddresses() {
var client = new HttpClient {BaseAddress = new Uri(_settingsService.GetHost())};
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var base64 = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "myuser", "test")));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",base64);
HttpResponseMessage response = await client.GetAsync("api/addresses");
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception(response.ReasonPhrase);
string content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Address[]>(content);
}
Is there a way to achieve calls with complex data types (lists of classes) as parameters?
Yes, The server application as ASP.NET or ASP.NET MVC or (preferably) ASP.NET WEB API can provide services with complex data types. In fact there is no limitation in declaring methods.
How do I secure the calls from the client? Simple http-logon?
There are wide ranage of authentication and authorization mechanism in ASP.NET (MVC, WEB API) which give you opportunity to choose one them.
The data transfers between your client and server via XML or JSON.
The "WebClient" class provides everything that you need to make a call from client to server.
More information:
http://www.codeproject.com/Articles/33798/HTTP-GET-with-NET-WebClient
How to post data to specific URL using WebClient in C#
How do I log into a site with WebClient?
Related
I'm trying to make a connection between my ASP.NET Core 3.0 Blazor (server-side) application and the Azure SignalR Service. I'll end up injecting my SignalR client (service) in to a few Blazor components so they'll update my UI/DOM in realtime.
My issue is that I'm receiving the following message when I call my .StartAsync() method on the hub connection:
Response status code does not indicate success: 404 (Not Found).
BootstrapSignalRClient.cs
This file loads my configuration for the SignalR Service including the URL, connection string, key, method name, and hub name. These settings are captured in the static class SignalRServiceConfiguration and used later.
public static class BootstrapSignalRClient
{
public static IServiceCollection AddSignalRServiceClient(this IServiceCollection services, IConfiguration configuration)
{
SignalRServiceConfiguration signalRServiceConfiguration = new SignalRServiceConfiguration();
configuration.Bind(nameof(SignalRServiceConfiguration), signalRServiceConfiguration);
services.AddSingleton(signalRServiceConfiguration);
services.AddSingleton<ISignalRClient, SignalRClient>();
return services;
}
}
SignalRServiceConfiguration.cs
public class SignalRServiceConfiguration
{
public string ConnectionString { get; set; }
public string Url { get; set; }
public string MethodName { get; set; }
public string Key { get; set; }
public string HubName { get; set; }
}
SignalRClient.cs
public class SignalRClient : ISignalRClient
{
public delegate void ReceiveMessage(string message);
public event ReceiveMessage ReceiveMessageEvent;
private HubConnection hubConnection;
public SignalRClient(SignalRServiceConfiguration signalRConfig)
{
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConfig.Url + signalRConfig.HubName)
.Build();
}
public async Task<string> StartListening(string id)
{
// Register listener for a specific id
hubConnection.On<string>(id, (message) =>
{
if (ReceiveMessageEvent != null)
{
ReceiveMessageEvent.Invoke(message);
}
});
try
{
// Start the SignalR Service connection
await hubConnection.StartAsync(); //<---I get an exception here
return hubConnection.State.ToString();
}
catch (Exception ex)
{
return ex.Message;
}
}
private void ReceiveMessage(string message)
{
response = JsonConvert.DeserializeObject<dynamic>(message);
}
}
I have experience using SignalR with .NET Core where you add it so the Startup.cs file using .AddSignalR().AddAzureSignalR() and map a hub in the app config and doing it this way requires certain 'configuration' parameters to be established (i.e. connection string).
Given my situation, where does HubConnectionBuilder get the connection string or a key to authenticate to the SignalR Service?
Is it possible the 404 message is a result of the missing key/connection string?
Okay so it turns out the documentation is lacking a key piece of information here. If you're using the .NET SignalR Client connecting to the Azure SignalR Service, you need to request a JWT token and present it when creating the hub connection.
If you need to authenticate on behalf of a user you can use this example.
Otherwise, you can set up a "/negotiate" endpoint using a web API such as an Azure Function to retrive a JWT token and client URL for you; this is what I ended up doing for my use case. Information about creating an Azure Function to get your JWT token and URL can be found here.
I created a class to hold these two values as such:
SignalRConnectionInfo.cs
public class SignalRConnectionInfo
{
[JsonProperty(PropertyName = "url")]
public string Url { get; set; }
[JsonProperty(PropertyName = "accessToken")]
public string AccessToken { get; set; }
}
I also created a method inside my SignalRService to handle the interaction with the web API's "/negotiate" endpoint in Azure, the instantiation of the hub connection, and the use of an event + delegate for receiving messages as follows:
SignalRClient.cs
public async Task InitializeAsync()
{
SignalRConnectionInfo signalRConnectionInfo;
signalRConnectionInfo = await functionsClient.GetDataAsync<SignalRConnectionInfo>(FunctionsClientConstants.SignalR);
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConnectionInfo.Url, options =>
{
options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
})
.Build();
}
The functionsClient is simply a strongly typed HttpClient pre-configured with a base URL and the FunctionsClientConstants.SignalR is a static class with the "/negotiate" path which is appended to the base URL.
Once I had this all set up I called the await hubConnection.StartAsync(); and it "connected"!
After all this I set up a static ReceiveMessage event and a delegate as follows (in the same SignalRClient.cs):
public delegate void ReceiveMessage(string message);
public static event ReceiveMessage ReceiveMessageEvent;
Lastly, I implemented the ReceiveMessage delegate:
await signalRClient.InitializeAsync(); //<---called from another method
private async Task StartReceiving()
{
SignalRStatus = await signalRClient.ReceiveReservationResponse(Response.ReservationId);
logger.LogInformation($"SignalR Status is: {SignalRStatus}");
// Register event handler for static delegate
SignalRClient.ReceiveMessageEvent += signalRClient_receiveMessageEvent;
}
private async void signalRClient_receiveMessageEvent(string response)
{
logger.LogInformation($"Received SignalR mesage: {response}");
signalRReservationResponse = JsonConvert.DeserializeObject<SignalRReservationResponse>(response);
await InvokeAsync(StateHasChanged); //<---used by Blazor (server-side)
}
I've provided documentation updates back to the Azure SignalR Service team and sure hope this helps someone else!
Update: the sample with the serverless sample is deprecated for the management SDK (sample). The management SDK uses a negotiation server.
I created WCF service and calling it from the android studio and its working fine, but after implementing WCF perSession functionality it is working for a single user at a time.
Problem:
My problem is when i am hitting WCF url with multiple user my sessionID get overwrite by the latest logged in user, So how to maintain session for multiple user like we do in web application.
I used this to creste session in WCF:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
and this is my method to create sessionid within services class
public class MyService : IMyService
{
static string currentSessionID = string.Empty;
public string createSession()
{
currentSessionID = DateTime.Now.Ticks.ToString();
return currentSessionID;
}
public string Login()
{
var mysessionId = createSession();
return mysessionId;
}
public string Mymethods(string data)
{
string response = "";
if(data.StartsWith("01"))
response = Login();
return response;
}
}
and am hitting this createSession() method only in login function.
Please help me out of this.....
Thanks in advnance.
Is this an acceptable implementation of a custom bearer token authorization mechanism?
Authorization Attribute
public class AuthorizeAttribute : TypeFilterAttribute
{
public AuthorizeAttribute(): base(typeof(AuthorizeActionFilter)){}
}
public class AuthorizeActionFilter : IAsyncActionFilter
{
private readonly IValidateBearerToken _authToken;
public AuthorizeActionFilter(IValidateBearerToken authToken)
{
_authToken = authToken;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
const string AUTHKEY = "authorization";
var headers = context.HttpContext.Request.Headers;
if (headers.ContainsKey(AUTHKEY))
{
bool isAuthorized = _authToken.Validate(headers[AUTHKEY]);
if (!isAuthorized)
context.Result = new UnauthorizedResult();
else
await next();
}
else
context.Result = new UnauthorizedResult();
}
}
Validation Service. APISettings class is used in appSettings, but validation can be extended to use a database ... obviously :)
public class APISettings
{
public string Key { get; set; }
}
public class ValidateBearerToken : IValidateBearerToken
{
private readonly APISettings _bearer;
public ValidateBearerToken(IOptions<APISettings> bearer)
{
_bearer = bearer.Value;
}
public bool Validate(string bearer)
{
return (bearer.Equals($"Bearer {_bearer.Key}"));
}
}
Implementation
[Produces("application/json")]
[Route("api/my")]
[Authorize]
public class MyController : Controller
appSettings
"APISettings": {
"Key": "372F78BC6B66F3CEAF705FE57A91F369A5BE956692A4DA7DE16CAD71113CF046"
}
Request Header
Authorization: Bearer 372F78BC6B66F3CEAF705FE57A91F369A5BE956692A4DA7DE16CAD71113CF046
That would work, but it's kind of reinventing the wheel.
I good approach these days is to use JWTs, you can find more info about it here: http://www.jwt.io/
Some advantages are that it integrates quite nicely with asp.net core and you can also add some information to the token (username, role, etc). That way, you don't even need to access the database for validation (if you want to).
Also, storing keys in appsettings file could lead to accidentally adding them to your source-code manager (security). You could use user secrets for local development (or disable the key when environment = dev) and environment variables for production.
Here is one good example of how to use jwt with asp.net: https://jonhilton.net/2017/10/11/secure-your-asp.net-core-2.0-api-part-1-issuing-a-jwt/
I've built a RESTful API (using ASP.NET Web API 2) which is only meant to be consumed from a single end-point. This end-point is a basic front-end site containing only HTML/CSS/JS. Due to various reasons, the front-end site and the API are completely external from one-another, with the front-end site being whitelisted in the API's CORS configuration.
I'm now trying to lock-down the API so that it's only accessible from this particular end-point, without introducing a new login system, because the context of where this page lives ensures that anyone accessing it is already a trusted user (it's technically behind a login system, but the page consuming the API has almost no knowledge of this context).
At a high level, I'd like to introduce a statically defined API Key of some sort, that would be hardcoded into both the API and the JavaScript of the consuming page, to help ensure that it's the only end-point accessing the API. We can assume that all communications between the front-end page and the API will be over a secure SSL/TLS connection.
My question: for such a case where I want to authenticate API requests from a particular page with a statically-defined API Key, what would be my best option from an ease-of-implementation standpoint? Most of the articles that I've found on Web API Authorization pivot around a user login system and seem grossly over-engineered for my particular use-case. I'd consider myself a novice when it comes to the subject and so I'm really just hoping for someone to point me in the right direction.
Thanks!
It seems like you are looking for a global filter in this specific case.
An authentication filter is a component that authenticates an HTTP request
You would basically send the shared / static api key with every request in the Authorization header and the custom filter would process this and decide whether the request is valid or not.
A basic implementation of the filter:
public class ApiKeyAuthenticationAttribute : IAuthenticationFilter
{
public bool AllowMultiple { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
// Get Auth header
AuthenticationHeaderValue authorization = request.Headers.Authorization;
// Validate the static token
if (authorization?.Parameter == "123")
{
IPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new Claim("CLAIMTYPE", "CLAIMVALUE") }));
context.Principal = principal;
}
else
{
context.ErrorResult = new AuthenticationFailureResult(request);
}
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
var challenge = new AuthenticationHeaderValue("Basic");
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
return Task.FromResult(0);
}
}
And to enable it for all calls to your api add it to your WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Some more config here
config.Filters.Add(new IdentityBasicAuthenticationAttribute());
}
}
The AuthenticationFailureResult and AddChallengeOnUnauthorizedResult are implementations of IHttpActionResult. For comprehensiveness I will add them here.
AuthenticationFailureResult
class AuthenticationFailureResult : IHttpActionResult
{
private HttpRequestMessage _request;
public AuthenticationFailureResult(HttpRequestMessage request)
{
_request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = _request;
response.Content = new StringContent("ACCESS DENIED MESSAGE");
return Task.FromResult(response);
}
}
AddChallengeOnUnauthorizedResult
class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
{
Challenge = challenge;
InnerResult = innerResult;
}
public AuthenticationHeaderValue Challenge { get; private set; }
public IHttpActionResult InnerResult { get; private set; }
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Only add one challenge per authentication scheme.
if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
{
response.Headers.WwwAuthenticate.Add(Challenge);
}
}
return response;
}
}
This code is from or a derivative of this article Authentication Filters in ASP.NET Web API 2 and this article Authentication Filters in ASP.NET Web API 2
I'm working on ASP.NET MVC5 project which has forms authentication enabled. Project is currently in test phase, and hosted online on Azure, but project owner would like to disable all public access to the site (since some parts of site don't require for user to authenticate at all).
For this test phase, we've decided to implement basic HTTP authentication, from this link. I've changed the code, so it better suits my needs:
public class BasicAuthenticationAttribute : FilterAttribute, IAuthorizationFilter
{
public string BasicRealm { get; set; }
protected string Username { get; set; }
protected string Password { get; set; }
public BasicAuthenticationAttribute(string username, string password)
{
this.Username = username;
this.Password = password;
}
public void OnAuthorization (AuthorizationContext filterContext)
{
var req = filterContext.HttpContext.Request;
var auth = req.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password)
return;
}
var res = filterContext.HttpContext.Response;
var alreadySent = HttpContext.Current.Items.Contains("headers-sent");
if (!alreadySent)
{
res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AppendHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Test"));
}
}
}
I've also registered it as a global filter:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorExtendedAttribute());
filters.Add(new BasicAuthenticationAttribute(AppConfig.BasicUsername, AppConfig.BasicPassword));
}
}
However, there are some issues when I run the project. If I use this version of code:
if (!alreadySent)
{
res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AppendHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Test"));
}
after successfull login it constantly redirects to forms login page.
However if I append
res.End();
after
res.AppendHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Test"));
Antiforgerytoken in cshtml files throws System.Web.HttpException
Server cannot append header after HTTP headers have been sent.
But in this case, it eventually successfully authenticates.
I'm currently stuck on this, and have no idea how to solve this problem, since turning off forms authentication is not and option, and I can't remove all AntiForgeryTokens and their validation.
I would suggest you to use ASP.NET Identity membership provider since it is included in MVC 5. Using this you can simply authenticate users without writing a lot of code as it was with previous Membership. It can also use external login with internal cookies as if you use FormsAuthentication method. All that you need is to make simple configurations in code and you don't need to write your custom filters.
Have you tried this line before res.AppendHeader(..) ?
Response.ClearHeaders();
Instead of ending the request with req.End(), I think you want to set filterContext.Result as shown below. Setting the Result to a non-null value will interrupt the processing of the rest of the MVC pipeline and cause the response to be sent to the browser, but it should not seal the response as End() does, so you shouldn't get the exception about headers already being sent.
Try this:
filterContext.Result = new HttpUnauthorizedResult();