I would like to implement a HTTPS connection for my rest webservice.
The HTTP version works as well, but when i try to connect over HTTPS and send a XML file or something else, It already fails when establishing the connection via https.
Has someone an idea what i can change to test it over https?
Startup.cs:
using System;
using Owin;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using AppFunc = System.Func<System.Collections.Generic.IDictionary<string, object>, System.Threading.Tasks.Task>;
[assembly: OwinStartup(typeof(SimuXmlDcs.MsiWebServer.Startup))]
namespace SimuXmlDcs.MsiWebServer
{
using System.Configuration;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Web.Http;
using Microsoft.Owin.Security;
using Newtonsoft.Json;
using SimuXmlDcs.MsiWebServer.App_Start;
using SimuXmlDcs.MsiWebServer.Controllers;
/// <summary>
/// The startup.
/// </summary>
public class Startup
{
/// <summary>
/// The configuration.
/// </summary>
/// <param name="app">
/// The app.
/// </param>
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(name: "SystemAction", routeTemplate: "api/{controller}/{system}/{action}", defaults: new { action = RouteParameter.Optional });
config.Routes.MapHttpRoute(name: "System", routeTemplate: "api/{controller}/{system}");
config.Routes.MapHttpRoute(name: "Info", routeTemplate: "api/{controller}");
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.XmlFormatter.UseXmlSerializer = true;
app.UseWebApi(config);
//byte[] test = new byte[4];
//test[0] = 10;
//test[1] = 78;
//test[2] = 2;
//test[3] = 193;
//IPAddress ipaddress = new IPAddress(test);
//TcpListener server = new TcpListener(ipaddress, 8443);
//server.Start();
//TcpClient client = server.AcceptTcpClient();
//SslStream stream = new SslStream(client.GetStream(), false, VerifyClientCertificate, null);
}
private static bool VerifyClientCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
/// <summary>
/// Setup authorization server
/// </summary>
/// <param name="app">
/// The app.
/// </param>
private void ConfigureOAuth(IAppBuilder app)
{
int timeSpan;
AppSettingsReader asr = new AppSettingsReader();
int.TryParse(asr.GetValue("TokenExpireInMinutes", typeof(string)).ToString(), out timeSpan);
app.UseOAuthAuthorizationServer(
new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = !MsiRestServer.UseHttps,
TokenEndpointPath = new PathString("/api/getsecuretoken"),
AccessTokenExpireTimeSpan = timeSpan != 0 ? TimeSpan.FromMinutes(timeSpan) : TimeSpan.FromDays(1),
Provider = new AuthorizationServerProvider(),
ApplicationCanDisplayErrors = true
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
}
AuthorizationServerProvider
namespace SimuXmlDcs.MsiWebServer.App_Start
{
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using SimuXmlDcs.MsiWebServer.Models;
/// <summary>
/// The authorization server provider.
/// </summary>
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
/// <summary>
/// The validate client authentication.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
/// <summary>
/// The grant resource owner credentials.
/// </summary>
/// <param name="context">
/// The context.
/// </param>
/// <returns>
/// The <see cref="Task"/>.
/// </returns>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
if (context.Password != "password")
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Role, RoleName.Admin));
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
context.Validated(new AuthenticationTicket(identity, new AuthenticationProperties { }));
}
}
}
MsiRestServer
namespace SimuXmlDcs.MsiWebServer
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using log4net;
using Microsoft.Owin.Hosting;
/// <summary>
/// The msi rest server.
/// </summary>
public static class MsiRestServer
{
private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static Thread msiWebServer;
private static bool endServer = false;
/// <summary>
/// Gets or sets a value indicating whether use https.
/// </summary>
public static bool UseHttps { get; set; }
/// <summary>
/// Gets or sets the base address.
/// </summary>
public static string BaseAddress { get; set; } = "https://test2234:8443";
/// <summary>
/// The startup server.
/// </summary>
public static void StartupServer()
{
Thread.Sleep(200);
endServer = false;
msiWebServer = new Thread(ServerThread);
msiWebServer.Start();
}
/// <summary>
/// The stop server.
/// </summary>
public static void StopServer()
{
endServer = true;
}
/// <summary>
/// The server thread.
/// </summary>
private static void ServerThread()
{
try
{
Uri tstAddress = new Uri(BaseAddress);
//WebServiceHost svcHost = new WebServiceHost();
// Start OWIN host
using (WebApp.Start<Startup>(url: BaseAddress))
{
while (!endServer)
{
Thread.Sleep(250);
}
}
}
catch (Exception ex)
{
logger.Error(ex);
MessageBox.Show(ex.Message);
}
}
}
}
For a self hosted app you must create a certificate.
http://chavli.com/how-to-configure-owin-self-hosted-website-with-ssl/
But when you use an IIS you only need to enable the project property "SSL enabled".
Related
I have the following code for a MQTT Subscriber in a Background Task:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PickByLight.BackgroundTask.Models;
using PickByLight.Database.Wrapper.Interfaces;
using PickByLight.Logic;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
namespace PickByLight.BackgroundTask
{
/// <summary>
/// Hosted MQTT Background Service
/// </summary>
public class HostedMQTTService : IHostedService, IDisposable
{
private readonly Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
/// <summary>
/// MQTT Client
/// </summary>
private MqttClient MqttClient { get; set; }
/// <summary>
/// Name of the Pick by Light
/// </summary>
private string PickByLight_Name { get; set; }
/// <summary>
/// MQTT is activated
/// </summary>
private bool MqttIsActive { get; set; }
/// <summary>
/// IP Adress of the MQTT URL
/// </summary>
private string MqttURL { get; set; }
/// <summary>
/// Storage Process for an material
/// </summary>
private MaterialStorageProcess StorageProcess { get; set; }
/// <summary>
/// Service Scope Factory
/// </summary>
private IServiceScopeFactory ServiceScopeFactory { get; set; }
/// <summary>
/// Configuration
/// </summary>
private IConfiguration Configuration { get; set; }
/// <summary>
/// Logger
/// </summary>
private readonly ILogger<HostedMQTTService> _logger;
/// <summary>
/// Constructor
/// </summary>
/// <param name="configuration"></param>
public HostedMQTTService(IConfiguration configuration, ILogger<HostedMQTTService> logger, IServiceScopeFactory serviceScopeFactory)
{
this.PickByLight_Name = configuration.GetValue<string>("PickByLight_Name");
this.MqttURL = configuration.GetValue<string>("MQTTUrl");
this.MqttIsActive = configuration.GetValue<bool>("MQTTConnection");
this.ServiceScopeFactory = serviceScopeFactory;
this.Configuration = configuration;
this._logger = logger;
}
/// <summary>
/// Start the Task of the Background Service
/// </summary>
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Background-Service started...");
while (true)
{
try
{
//No Object is created
if (this.MqttClient == null)
{
_logger.LogInformation("Try to establishe new MQTT Client");
this.MqttClient = CreateNewMqttConnection();
}
else if (this.MqttClient.IsConnected == false)
{
_logger.LogInformation("MQTT Client is disconnected... Try to reconnect!");
this.MqttClient = CreateNewMqttConnection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ein schwerwiegender Fehler im MQTT Background-Service ist aufgetreten.");
}
}
}
/// <summary>
/// Prints out all received messages
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Mqtt_Message_Received(object sender, MqttMsgPublishEventArgs e)
{
try
{
var agcMessage = Encoding.UTF8.GetString(e.Message);
_logger.LogInformation("Topic: " + e.Topic + " | Nachricht: " + agcMessage + " | QOS: " + e.QosLevel);
var resultString = Encoding.UTF8.GetString(e.Message);
MqttReadTopicClass mqttContent = JsonConvert.DeserializeObject<MqttReadTopicClass>(resultString);
using (var scope = this.ServiceScopeFactory.CreateScope())
{
var storageConfigurationManager = scope.ServiceProvider.GetService<IStorageConfigurationManager>();
var storageElementManager = scope.ServiceProvider.GetService<IStorageElementManager>();
this.StorageProcess = new MaterialStorageProcess(storageConfigurationManager, storageElementManager, this.Configuration);
StorageProcess.Remove(mqttContent.storageLocation);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Schwerwiegender Fehler beim Lesen von MQTT Nachrichten");
}
}
/// <summary>
/// Create new MQTT connection if connection is lost or doesn't exist
/// </summary>
private MqttClient CreateNewMqttConnection()
{
_logger.LogInformation("Create MQTT Client");
MqttClient client = new MqttClient(this.MqttURL, 32005, false, null, null, MqttSslProtocols.None);
string clientId = Guid.NewGuid().ToString();
client.MqttMsgPublishReceived += Mqtt_Message_Received;
client.Connect(clientId);
client.Subscribe(new string[] { "buttonpress_sepioo_pdi/" + this.PickByLight_Name }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
_logger.LogInformation("MQTT Client created");
return client;
}
/// <summary>
/// Stop the Task of the Background Service
/// </summary>
public async Task StopAsync(CancellationToken cancellationToken)
{
//Stop called without start
if (_executingTask == null)
{
return;
}
try
{
//Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
//wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
/// <summary>
/// Dispose the Background Service
/// </summary>
public void Dispose()
{
_stoppingCts.Cancel();
}
}
}
In my startup.cs File i am doing the following:
//Register Background Task
services.AddHostedService<HostedMQTTService>();
The problem is, that it seems to me that the hosted service is blocking the user-interface/webserver threads because i can not access the url of the .net 6 mvc application.
Could you give me a hint or a solution to this problem?
Thanks.
You will need to change your StartAsync-method to something like this:
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
_logger.LogInformation("Background-Service started...");
while (!cancellationToken.IsCancellationRequested)
{
try
{
//No Object is created
if (this.MqttClient == null)
{
_logger.LogInformation("Try to establish new MQTT Client");
this.MqttClient = CreateNewMqttConnection();
}
else if (this.MqttClient.IsConnected == false)
{
_logger.LogInformation("MQTT Client is disconnected... Try to reconnect!");
this.MqttClient = CreateNewMqttConnection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ein schwerwiegender Fehler im MQTT Background-Service ist aufgetreten.");
}
}
});
}
You are blocking the process, because your hosted service never starts.
Remove the while(true) loop from the Start method.
Ensure base.StartAsync(cancellationToken); is always called.
Edit: I saw you implement the IHostedService interface. Try to inherits from the BackgroundService class
I have an ASP.NET MVC project and one page of it must show my device state. I want get device state with MQTT from a broker (broker.mqttdashboard.com). I am using MQTTnet. I can not connect to broker , I have not username or password and I want to use a public channel to publish messages to broker by pressing a button. the button run PublishMqttMsg method and I want to give messages from broker.
I don't know how to connect to broker. Can anyone help me?
My controller code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
using System;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet;
using Microsoft.AspNet.Identity;
namespace WebApplication10.Controllers
{
public class MqttController : Controller
{
private static IManagedMqttClient client;
// GET: Mqtt
public ActionResult Index()
{
var CurrentUser = User.Identity.GetUserId();
ConnectAsync(CurrentUser, "broker.mqttdashboard.com", "", 8000, true);
client.UseConnectedHandler(e =>
{
Console.WriteLine("Connected successfully with MQTT Brokers.");
});
client.UseDisconnectedHandler(e =>
{
Console.WriteLine("Disconnected from MQTT Brokers.");
});
client.UseApplicationMessageReceivedHandler(e =>
{
try
{
string topic = e.ApplicationMessage.Topic;
if (string.IsNullOrWhiteSpace(topic) == false)
{
string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
Console.WriteLine($"Topic: {topic}. Message Received: {payload}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message, ex);
}
});
return View();
}
public ActionResult SubscribeMqttMsg()
{
SubscribeAsync("test_topic/1");
return View("Index");
}
public ActionResult PublishMqttMsg()
{
PublishAsync("test_topic/1", "test message");
return View("Index");
}
/// <summary>
/// Connect to broker.
/// </summary>
/// <returns>Task.</returns>
public static async Task ConnectAsync(string user, string uri, string pass, int port, bool userSSL)
{
string clientId = Guid.NewGuid().ToString();
string mqttURI = uri;
string mqttUser = user;
string mqttPassword = pass;
int mqttPort = port;
bool mqttSecure = userSSL;
var messageBuilder = new MqttClientOptionsBuilder()
//.WithClientId(clientId)
//.WithCredentials(mqttUser, mqttPassword)
.WithTcpServer(mqttURI, mqttPort)
.WithCleanSession();
var options = mqttSecure
? messageBuilder
.WithTls()
.Build()
: messageBuilder
.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(options)
.Build();
client = new MqttFactory().CreateManagedMqttClient();
await client.StartAsync(managedOptions);
}
/// <summary>
/// Publish Message.
/// </summary>
/// <param name="topic">Topic.</param>
/// <param name="payload">Payload.</param>
/// <param name="retainFlag">Retain flag.</param>
/// <param name="qos">Quality of Service.</param>
/// <returns>Task.</returns>
public static async Task PublishAsync(string topic, string payload, bool retainFlag = true, int qos = 1) =>
await client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.WithRetainFlag(retainFlag)
.Build())
;
/// <summary>
/// Subscribe topic.
/// </summary>
/// <param name="topic">Topic.</param>
/// <param name="qos">Quality of Service.</param>
/// <returns>Task.</returns>
public static async Task SubscribeAsync(string topic, int qos = 1) =>
await client.SubscribeAsync(new TopicFilterBuilder()
.WithTopic(topic)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.Build());
}
}
Port 8000 at broker.hivemq.com is the "Websocket Port" (so listening for MQTT over websockets connections). You are attempting to connect using a TCP client. Either change the port (to 1833) or change:
var messageBuilder = new MqttClientOptionsBuilder()
.WithTcpServer(mqttURI, mqttPort)
...
to:
var messageBuilder = new MqttClientOptionsBuilder()
.WithWebSocketServer("broker.hivemq.com:8000/mqtt")
...
(see the documentation).
I have an odata/webapi app that's been updated to use OWIN auth. The code to issue tokens (/token url) works fine and returns a token, but any "normal" call to any controllers returns HTTP 400
{"error": "unsupported_grant_type"}
with no additional information.
Startup.cs:
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Web.Http;
[assembly: OwinStartup(typeof(VP4C_API.Startup))]
namespace VP4C_API
{
public class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
app.UseOAuthBearerTokens(OAuthOptions);
app.UseOAuthBearerAuthentication(oAuthBearerAuthenticationOptions);
Statics.Config = new HttpConfiguration();
WebApiConfig.Register(Statics.Config);
app.UseWebApi(Statics.Config);
}
/// <summary>
/// Called at when the AppPool starts the website
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
}
AuthorizationProvider which returns the token:
using System;
using System.Threading.Tasks;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
namespace VP4C_API
{
internal class SimpleAuthorizationServerProvider : IOAuthAuthorizationServerProvider
{
Task IOAuthAuthorizationServerProvider.AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
{
throw new NotImplementedException();
}
Task IOAuthAuthorizationServerProvider.AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
throw new NotImplementedException();
}
Task IOAuthAuthorizationServerProvider.GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Take the ClientID and validate its one we want to talk to
/// Called after <see cref="IOAuthAuthorizationServerProvider.ValidateTokenRequest"/> />
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
if (context.ClientId == "App1Test")
{
var identity = new ClaimsIdentity(new GenericIdentity(
context.ClientId, OAuthDefaults.AuthenticationType),
context.Scope.Select(x => new Claim("urn:oauth:scope", x))
);
context.Validated(identity);
}
else
{
context.Rejected();
context.SetError("Unknown client id", "You must list the client id in the known clients table");
}
return Task.FromResult(0);
}
Task IOAuthAuthorizationServerProvider.GrantCustomExtension(OAuthGrantCustomExtensionContext context)
{
throw new NotImplementedException();
}
Task IOAuthAuthorizationServerProvider.GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.ValidateTokenRequest"/>
/// Check the username/password here
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (context.UserName == "test" && context.Password == "test")
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
System.Security.Principal.GenericIdentity gi = new System.Security.Principal.GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType);
System.Security.Claims.ClaimsIdentity ci = new System.Security.Claims.ClaimsIdentity(gi, context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
var ticket = new AuthenticationTicket(ci, null);
context.Validated(ticket);
}
else
{
context.SetError("GrantResourceOwner Error", "Username or Password is incorrect.");
context.Rejected();
}
return Task.FromResult(0);
}
/// <summary>
/// Called first to filter on the endpoint attributes (e.g. client application IP address)
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.MatchEndpoint(OAuthMatchEndpointContext context)
{
context.MatchesTokenEndpoint();
return Task.FromResult(0);
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.GrantResourceOwnerCredentials"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.TokenEndpoint(OAuthTokenEndpointContext context)
{
AuthenticationProperties ap = new AuthenticationProperties();
ap.AllowRefresh = true;
ap.IssuedUtc = DateTime.UtcNow;
ap.IsPersistent = true;
context.Issue(context.Identity, ap);
return Task.FromResult(0);
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.TokenEndpoint"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
return Task.FromResult(0);
}
Task IOAuthAuthorizationServerProvider.ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.MatchEndpoint"/>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientID;
string clientPassword;
context.TryGetBasicCredentials(out clientID, out clientPassword);
context.Validated(clientID);
return Task.FromResult(0);
}
Task IOAuthAuthorizationServerProvider.ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
throw new NotImplementedException();
}
/// <summary>
/// Called after <see cref="IOAuthAuthorizationServerProvider.ValidateClientAuthentication"/>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task IOAuthAuthorizationServerProvider.ValidateTokenRequest(OAuthValidateTokenRequestContext context)
{
context.Validated();
return Task.FromResult(0);
}
}
}
And a sample controller snippet for http://host/api/Verify/1
[HttpGet, AllowAnonymous, Route("{companyID}")]
public IHttpActionResult Verify(int companyID)
{
string cs = "not set";
int check = -1;
try
{
ErrorLogging.Logging.Information(null, null, Request.RequestUri, $"Verify Start company {companyID}");
Any suggestions on where to look?
I need to have both windows authentication and owin (forms) authentication but i can't get it to work.
Probably the best option is to have two sites that have different authentication methods.
I found a project that does what i want: MVC5-MixedAuth. But it uses IISExpress and i can't get it to work with Local IIS.
The error that occurs is:
Request filtering is configured on the Web server to deny the request because the query string is too long.
If i remove all my ConfigureAuth() method inside Startup.Auth.cs it doesn't throw the error but i can't login because it is needed to do CookieAuthentication.
Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(dbEmployeePortal.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, UserMaster, int>
(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))
)
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
Any idea?
UPDATE 1
The error
Request filtering is configured on the Web server to deny the request because the query string is too long.
appears because occurs a login loop when it tries to reach the login page.
Resolved!
I followed the example: MVC5-MixAuth
Credits: Mohammed Younes
UPDATE 1
Problem: I needed to have both Anonymous Authentication and Windows Authentication enabled.
But when you enable them both, you can only get NT AUTHORITY\IUSR.
Resolution: To get the current user (introduced with NTLM prompt), we need to create an handler that will execute when an user enter at login page.
When the user hits the login page, the handler will get the current windows identity cached in the browser and then set as the LogonUserIdentity.
Note: I needed to use windows first login, when the user hits the login page it will try to get the correspondent ASP.NET User.
Handler
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.Identity;
namespace MixedAuth
{
/// <summary>
/// Managed handler for windows authentication.
/// </summary>
public class WindowsLoginHandler : HttpTaskAsyncHandler, System.Web.SessionState.IRequiresSessionState
{
public HttpContext Context { get; set; }
public override async Task ProcessRequestAsync(HttpContext context)
{
this.Context = context;
//if user is already authenticated, LogonUserIdentity will be holding the current application pool identity.
//to overcome this:
//1. save userId to session.
//2. log user off.
//3. request challenge.
//4. log user in.
if (context.User.Identity.IsAuthenticated)
{
this.SaveUserIdToSession(context.User.Identity.GetUserId());
await WinLogoffAsync(context);
context.RequestChallenge();
}
else if (!context.Request.LogonUserIdentity.IsAuthenticated)
{
context.RequestChallenge();
}
else
{
// true: user is trying to link windows login to an existing account
if (this.SessionHasUserId())
{
var userId = this.ReadUserIdFromSession();
this.SaveUserIdToContext(userId);
await WinLinkLoginAsync(context);
}
else // normal login.
await WinLoginAsync(context);
}
}
#region helpers
/// <summary>
/// Executes Windows login action against account controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLoginAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Login);
routeData.Values.Add("returnUrl", context.Request["returnUrl"]);
routeData.Values.Add("userName", context.Request.Form["UserName"]);
await ExecuteController(context, routeData);
}
/// <summary>
/// Execute Link Windows login action against account controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLinkLoginAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Link);
await ExecuteController(context, routeData);
}
/// <summary>
/// Executes Windows logoff action against controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLogoffAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Logoff);
await ExecuteController(context, routeData);
}
/// <summary>
/// Executes controller based on route data.
/// </summary>
/// <param name="context"></param>
/// <param name="routeData"></param>
/// <returns></returns>
private async Task ExecuteController(HttpContext context, RouteData routeData)
{
var wrapper = new HttpContextWrapper(context);
MvcHandler handler = new MvcHandler(new RequestContext(wrapper, routeData));
IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler)handler);
await Task.Factory.FromAsync(asyncHandler.BeginProcessRequest, asyncHandler.EndProcessRequest, context, null);
}
#endregion
}
}
Extensions
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
namespace MixedAuth
{
public enum Action { Login, Link, Logoff };
public static class MixedAuthExtensions
{
const string userIdKey = "windows.userId";
//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
const int fakeStatusCode = 418;
const string controllerName = "Account";
const string loginActionName = "WindowsLogin";
const string linkActionName = "LinkWindowsLogin";
const string logoffActionName = "WindowsLogoff";
const string windowsLoginRouteName = "Windows/Login";
public static void RegisterWindowsAuthentication(this MvcApplication app)
{
app.EndRequest += (object sender, EventArgs e) =>
{
HttpContext.Current.ApplyChallenge();
};
}
/// <summary>
/// Registers ignore route for the managed handler.
/// </summary>
/// <param name="routes"></param>
public static void IgnoreWindowsLoginRoute(this RouteCollection routes)
{
routes.IgnoreRoute(windowsLoginRouteName);
}
/// <summary>
/// By pass all middleware and modules, by setting a fake status code.
/// </summary>
/// <param name="context"></param>
public static void RequestChallenge(this HttpContext context)
{
context.Response.StatusCode = fakeStatusCode;
}
/// <summary>
/// Invoke on end response only. Replaces the current response status code with 401.2
/// </summary>
/// <param name="context"></param>
public static void ApplyChallenge(this HttpContext context)
{
if (context.Response.StatusCode == fakeStatusCode)
{
context.Response.StatusCode = 401;
context.Response.SubStatusCode = 2;
//http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors(v=vs.110).aspx
//context.Response.TrySkipIisCustomErrors = true;
}
}
/// <summary>
///
/// </summary>
/// <param name="handler"></param>
/// <param name="action"></param>
/// <returns></returns>
public static RouteData CreateRouteData(this WindowsLoginHandler handler, Action action)
{
RouteData routeData = new RouteData();
routeData.RouteHandler = new MvcRouteHandler();
switch (action)
{
case Action.Login:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", loginActionName);
break;
case Action.Link:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", linkActionName);
break;
case Action.Logoff:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", logoffActionName);
break;
default:
throw new NotSupportedException(string.Format("unknonw action value '{0}'.", action));
}
return routeData;
}
/// <summary>
/// Saves userId to the items collection inside <see cref="HttpContext"/>.
/// </summary>
public static void SaveUserIdToContext(this WindowsLoginHandler handler, string userId)
{
if (handler.Context.Items.Contains(userIdKey))
throw new ApplicationException("Id already exists in context.");
handler.Context.Items.Add("windows.userId", userId);
}
/// <summary>
/// Reads userId from item collection inside <see cref="HttpContext"/>.
/// </summary>
/// <remarks>The item will removed before this method returns</remarks>
/// <param name="context"></param>
/// <returns></returns>
public static int ReadUserId(this HttpContextBase context)
{
if (!context.Items.Contains(userIdKey))
throw new ApplicationException("Id not found in context.");
int userId = Convert.ToInt32(context.Items[userIdKey] as string);
context.Items.Remove(userIdKey);
return userId;
}
/// <summary>
/// Returns true if the session contains an entry for userId.
/// </summary>
public static bool SessionHasUserId(this WindowsLoginHandler handler)
{
return handler.Context.Session[userIdKey] != null;
}
/// <summary>
/// Save a session-state value with the specified userId.
/// </summary>
public static void SaveUserIdToSession(this WindowsLoginHandler handler, string userId)
{
if (handler.SessionHasUserId())
throw new ApplicationException("Id already exists in session.");
handler.Context.Session[userIdKey] = userId;
}
/// <summary>
/// Reads userId value from session-state.
/// </summary>
/// <remarks>The session-state value removed before this method returns.</remarks>
/// <param name="session"></param>
/// <returns></returns>
public static string ReadUserIdFromSession(this WindowsLoginHandler handler)
{
string userId = handler.Context.Session[userIdKey] as string;
if (string.IsNullOrEmpty(userIdKey))
throw new ApplicationException("Id not found in session.");
handler.Context.Session.Remove(userIdKey);
return userId;
}
/// <summary>
/// Creates a form for windows login, simulating external login providers.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes);
}
/// <summary>
/// Creates a form for windows login, simulating external login providers.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object routeValues, object htmlAttributes)
{
return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes);
}
}
}
Note
You need to have AccountController.cs as partial.
AccountController.Windows.cs
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.Identity;
using MixedAuth;
namespace EmployeePortal.Web.Controllers
{
[Authorize]
public partial class AccountController : BaseController
{
//
// POST: /Account/WindowsLogin
[AllowAnonymous]
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult WindowsLogin(string userName, string returnUrl)
{
if (!Request.LogonUserIdentity.IsAuthenticated)
{
return RedirectToAction("Login");
}
var loginInfo = GetWindowsLoginInfo();
// Sign in the user with this external login provider if the user already has a login
var user = UserManager.Find(loginInfo);
if (user != null)
{
SignIn(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
return RedirectToAction("Login", new RouteValueDictionary(new { controller = "Account", action = "Login", returnUrl = returnUrl }));
}
}
//
// POST: /Account/WindowsLogOff
[HttpPost]
[ValidateAntiForgeryToken]
public void WindowsLogOff()
{
AuthenticationManager.SignOut();
}
//
// POST: /Account/LinkWindowsLogin
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> LinkWindowsLogin()
{
int userId = HttpContext.ReadUserId();
//didn't get here through handler
if (userId <= 0)
return RedirectToAction("Login");
HttpContext.Items.Remove("windows.userId");
//not authenticated.
var loginInfo = GetWindowsLoginInfo();
if (loginInfo == null)
return RedirectToAction("Manage");
//add linked login
var result = await UserManager.AddLoginAsync(userId, loginInfo);
//sign the user back in.
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
await SignInAsync(user, false);
if (result.Succeeded)
return RedirectToAction("Manage");
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
#region helpers
private UserLoginInfo GetWindowsLoginInfo()
{
if (!Request.LogonUserIdentity.IsAuthenticated)
return null;
return new UserLoginInfo("Windows", Request.LogonUserIdentity.User.ToString());
}
#endregion
}
public class WindowsLoginConfirmationViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
}
}
Then, you need to add the handler:
<add name="Windows Login Handler" path="Login" verb="GET,POST" type="MixedAuth.WindowsLoginHandler" preCondition="integratedMode" />
Startup.cs
app.CreatePerOwinContext(dbEmployeePortal.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
PathString path = new PathString("/Account/Login");
if (GlobalExtensions.WindowsAuthActive)
path = new PathString("/Windows/Login");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
//LoginPath = new PathString("/Account/Login")
LoginPath = path
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
Then you need to configure Local IIS to use WindowsAuthentication and AnonymousAuthentication. You can do this in the Authentication Module.
Note If you don't have WindowsAuthentication go to Control Panel then Programs and Features then "Turn Windows features on or off":
select "Internet Information Services" > "World Wide Web" > "Security"
and select Windows Authentication.
I didn't see this in your answer, so for anyone looking on how to capture Windows Auth in your Owin pipeline, you also can add the following to your ConfigureAuth method:
public void ConfigureAuth(IAppBuilder app)
{
HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
}
I'm new to this forum and to have a question about await/async use in Xamarin (Also the first time I work with).
I am working for my internship on a project using Xamarin, PCL, MvvmCross.
In my PCL im do a postrequest to a WCF service to login in my application. In WP8 everything just works fine, but when I am running my application on Android the response is always null.
Below you can find my httpclient class. The method with the post is InternalPostAsync
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using Anton.Mobile.Shared.Infrastructure;
using System.Net;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
namespace Anton.Mobile.Shared.Data
{
public class AntonClient
{
#region static
/// <summary>
/// Base Uri of the Ria service (e.g. http://example.com/)
/// </summary>
private static readonly Uri _baseUri = new Uri(Config.BaseUri);
/// <summary>
/// Last cookie response header (Session, authentication, ...)
/// </summary>
private static string _cookieHeader = null;
/// <summary>
/// Lock object for read/write <para>_cookieHeader</para>
/// </summary>
private static object _lockObj = new object();
#endregion
#region protected
/// <summary>
/// Creates a client
/// </summary>
/// <param name="container">Cookie container to use</param>
/// <returns>HttpClient</returns>
protected virtual HttpClient CreateClient(CookieContainer container)
{
//set container on handler for tracking cookies between request-response
var handler = new HttpClientHandler()
{
CookieContainer = container,
UseCookies = true,
UseDefaultCredentials = false,
};
//Create client and set the base address
var cl = new HttpClient(handler)
{
BaseAddress = _baseUri
};
if (!string.IsNullOrEmpty(_cookieHeader))
{
cl.DefaultRequestHeaders.Add("Cookies", _cookieHeader);
}
return cl;
}
/// <summary>
/// Creates a JSON content request
/// </summary>
/// <param name="jsonContent">JSON value</param>
/// <returns>JSON content</returns>
protected virtual HttpContent CreateRequestContent(string jsonContent)
{
var content = new StringContent(jsonContent,Encoding.UTF8,"application/json");
//content.Headers.Add("ContentType", "application/json");
return content;
}
/// <summary>
/// Save cookies <para>_cookieHeader</para>
/// </summary>
/// <param name="container">cookie container</param>
protected void ParseCookies(HttpResponseMessage msg)
{
IEnumerable<string> values;
if (!msg.Headers.TryGetValues("Set-Cookie", out values) || !values.Any())
return;
//var cookies = container.GetCookieHeader(_baseUri);
var cs = new List<string>();
foreach (var v in values)
{
string[] vs = v.Split(new char[] { ';' });
string[] value = vs[0].Split(new char[] { '=' });
container.Add(new Uri("Http://initesting"), new Cookie(value[0], value[1]));
cs.Add(string.Format("{0}={1}", value[0], value[1]));
}
lock (_lockObj)
{
_cookieHeader = string.Join(";", cs.ToArray());
}
}
private static CookieContainer container = new CookieContainer();
/// <summary>
/// Create a new cookie container from <para>_cookieHeaders</para>
/// </summary>
/// <returns>Cookie container</returns>
protected CookieContainer CreateCookieContainer()
{
//lock (_lockObj)
//{
// if (!string.IsNullOrEmpty(_cookieHeader))
// {
// foreach (var header in _cookieHeader.Split(new char[] { ';' }))
// {
// container.SetCookies(_baseUri, header);
// }
// }
//}
return container;
}
/// <summary>
/// Executes a POST HTTP Request
/// </summary>
/// <param name="jsonContent">POST JSON content</param>
/// <param name="uri">Service uri</param>
/// <returns>Response content as string (JSON)</returns>
protected virtual async Task<string> InternalPostAsync(string jsonContent, Uri uri)
{
var container = CreateCookieContainer();
using (var client = CreateClient(container))
{
var content = CreateRequestContent(jsonContent);
var response = await client.PostAsync(uri, content);
if (response.StatusCode != HttpStatusCode.OK)
{
return null; //todo
}
ParseCookies(response);
return await response.Content.ReadAsStringAsync();
}
}
/// <summary>
/// Executes a GET HTTP Request
/// </summary>
/// <param name="uri">Service uri</param>
/// <returns>Response content as string (JSON)</returns>
protected virtual async Task<string> InternalRequestAsync(Uri uri)
{
var container = CreateCookieContainer();
using (var client = CreateClient(container))
{
HttpResponseMessage response = await client.GetAsync(uri);
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
ParseCookies(response);
return await response.Content.ReadAsStringAsync();
}
}
#endregion protected
#region public
/// <summary>
/// Executes a POST HTTP Request for a given Request key
/// </summary>
/// <typeparam name="TRequest">Request Type</typeparam>
/// <typeparam name="TResult">Result Type</typeparam>
/// <param name="request">Request POST value to JSON serializing</param>
/// <param name="key">Unique Request Key</param>
/// <returns>Deserialized POST response content of type TResult</returns>
public async Task<TResult> PostAsync<TRequest, TResult>(TRequest request, RequestKey key)
where TRequest : class
where TResult : class
{
try
{
var uri = RequestMap.GetUri(key);
string jsonResult = await InternalPostAsync(request.SerializeJson(), uri);
return jsonResult.DeserializeJson<TResult>();
}
catch (Exception)
{
//todo
}
return default(TResult);
}
/// <summary>
/// Executes a POST HTTP Request for a given service uri
/// </summary>
/// <typeparam name="TRequest">Request Type</typeparam>
/// <param name="request">Request POST value to JSON serializing</param>
/// <param name="uri">Service URI</param>
/// <returns>Deserialized POST response content of type dynamic</returns>
public async Task<dynamic> PostAsync<TRequest>(TRequest request, string uri)
{
try
{
string jsonResult = await InternalPostAsync(request.SerializeJson(), new Uri(uri, UriKind.Absolute));
return jsonResult.DynamicJson();
}
catch (Exception)
{
//todo
}
return null;
}
/// <summary>
/// Executes a GET HTTP Request for a givin key and query string parameter info
/// </summary>
/// <typeparam name="TResponse">Response Type</typeparam>
/// <param name="key">Unique request key</param>
/// <param name="queryString">Querystring info</param>
/// <returns>Deserialized POST response content of type TResult</returns>
public async Task<TResponse> RequestAsync<TResponse>(RequestKey key, IDictionary<string, string> queryString = null)
{
try
{
string jsonResult = await InternalRequestAsync(RequestMap.GetUri(key, queryString));
var dynamicResult = jsonResult.DynamicJson();
var item = (dynamicResult as JObject)[RequestMap.GetValue(key) + "Result"]["RootResults"].First; //todo: better solution for this
return item.ToObject<TResponse>();
}
catch (Exception)
{
//todo
}
return default(TResponse);
}
#endregion public
}
}
Regarding the website of Xamarin async/await is supported, but I did not find any similar problems. I hope you can help me.
I have solved my problem.
In Android, when you add the NuGet package with the HTTP Libraries, something went wrong when adding the references. You have to add the references mannualy to solve it. I also editted my Uri, now it works with the IP-address in the Uri.