I\'m aware that the authentication on the webservicehost class does not adhere fully to authentication standards (returns 403 forbidden rather than prompting for another set
Shaydo, you are the best! Thank you! That is what I searched for for weeks! I expanded the vb Code in order to use it with https: VB.NET:
Public Class AuthenticatedWebServiceHost
Inherits WebServiceHost
Public Sub New(ByVal type As Type, ByVal url As Uri, MyThumbprint As String)
Dim desc As IDictionary(Of String, ContractDescription) = Nothing
MyBase.InitializeDescription(type, New UriSchemeKeyedCollection())
MyBase.CreateDescription(desc)
Dim val = desc.Values.First()
Dim binding As WebHttpBinding = New WebHttpBinding()
'binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly
binding.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic
MyBase.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom
MyBase.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = New CustomUserNamePasswordValidator()
MyBase.Credentials.ClientCertificate.SetCertificate(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, MyThumbprint)
MyBase.AddServiceEndpoint(val.ContractType, binding, url)
End Sub
Public Shared ReadOnly Property UserName As String
Get
If OperationContext.Current Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity Is Nothing Then Return Nothing
Return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
End Get
End Property
Public Class CustomUserNamePasswordValidator
Inherits UserNamePasswordValidator
Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
If userName <> password Then
Console.WriteLine("Error: Access denied")
Throw New SecurityAccessDeniedException()
End If
End Sub
End Class
End Class
The answer provided by I4V worked like a charm, converted to VB and copied below in case anyone else needs it in future after spending many hours hunting the web.
The Line to call it is as per the code provided by I4V.
Dim varWebService = New AuthenticatedWebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))
VB.Net Code
Imports System.IdentityModel.Selectors
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports System.ServiceModel.Security
Imports System.ServiceModel.Web
Public Class AuthenticatedWebServiceHost
Inherits WebServiceHost
Public Sub New(ByVal type As Type, ByVal url As Uri)
Dim desc As IDictionary(Of String, ContractDescription) = Nothing
MyBase.InitializeDescription(type, New UriSchemeKeyedCollection())
MyBase.CreateDescription(desc)
Dim val = desc.Values.First()
Dim binding As WebHttpBinding = New WebHttpBinding()
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic
MyBase.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom
MyBase.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = New CustomUserNamePasswordValidator()
MyBase.AddServiceEndpoint(val.ContractType, binding, url)
End Sub
Public Shared ReadOnly Property UserName As String
Get
If OperationContext.Current Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity Is Nothing Then Return Nothing
Return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
End Get
End Property
Public Class CustomUserNamePasswordValidator
Inherits UserNamePasswordValidator
Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
If userName <> password Then Throw New SecurityAccessDeniedException()
End Sub
End Class
End Class
You can write a custom WebServiceHost by inheriting from it and change some default parameters like below.
The only change in your code would be
Dim varWebService = New AuthenticatedWebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IdentityModel;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Security;
using System.ServiceModel.Description;
namespace StackOverflow
{
public class AuthenticatedWebServiceHost : WebServiceHost
{
public AuthenticatedWebServiceHost(Type type, Uri url)
{
IDictionary<string, ContractDescription> desc = null;
base.InitializeDescription(type, new UriSchemeKeyedCollection());
base.CreateDescription(out desc);
var val = desc.Values.First();
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
base.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
base.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();
base.AddServiceEndpoint(val.ContractType, binding, url);
}
//Possible next question:
//"How can I get the name of the authenticated user?"
public static string UserName
{
get
{
if (OperationContext.Current == null) return null;
if (OperationContext.Current.ServiceSecurityContext == null) return null;
if (OperationContext.Current.ServiceSecurityContext.PrimaryIdentity == null) return null;
return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
}
}
public class CustomUserNamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
//Your logic to validate username/password
if (userName != password)
throw new SecurityAccessDeniedException();
}
}
}
}