WCF Rest Token based authentication - wcf

I am working on WCF Rest application I need to implement the token based authentication in it.Please suggest me a perfect way to implement the token based authentication WCF Rest.

I was able to implement AAD Token based authentication in a WCF based SOAP service.
For this I leveraged WCF extensibility features - Message Inspector and Custom Invoker in the following way
Message Inspector : Using the message inspector, we extract the Bearer Token from the Authorization Header of the incoming request. Post this we perform the token validation using OIDC library to get the keys and config for Microsoft AAD. If token is validated, the operation is invoked and we get the response on the client side.
If the Token validation fails, we stop the request processing by using Custom Invoker and return 401 Unauthorized response to the caller with a custom error message.
public class BearerTokenMessageInspector : IDispatchMessageInspector
{
/// Method called just after request is received. Implemented by default as defined in IDispatchMessageInspector
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
WcfErrorResponseData error = null;
var requestMessage = request.Properties["httpRequest"] as HttpRequestMessageProperty;
if (request == null)
{
error = new WcfErrorResponseData(HttpStatusCode.BadRequest, string.Empty, new KeyValuePair<string, string>("InvalidOperation", "Request Body Empty."));
return error;
}
var authHeader = requestMessage.Headers["Authorization"];
try
{
if (string.IsNullOrEmpty(authHeader))
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Error: Authorization Header empty! Please pass a Token using Bearer scheme."));
}
else if (this.Authenticate(authHeader))
{
return null;
}
}
catch (Exception e)
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Token with Client ID \"" + clientID + "\" failed validation with Error Messsage - " + e.Message));
}
if (error == null) //Means the token is valid but request must be unauthorized due to not-allowed client id
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Token with Client ID \"" + clientID + "\" failed validation with Error Messsage - " + "The client ID: " + clientID + " might not be in the allowed list."));
}
//This will be checked before the custom invoker invokes the method, if unauthorized, nothing is invoked
OperationContext.Current.IncomingMessageProperties.Add("Authorized", false);
return error;
}
/// Method responsible for validating the token and tenantID Claim.
private bool Authenticate(string authHeader)
{
const string bearer = "Bearer ";
if (!authHeader.StartsWith(bearer, StringComparison.InvariantCultureIgnoreCase)) { return false; }
var jwtToken = authHeader.Substring(bearer.Length);
PopulateIssuerAndKeys();
var validationParameters = GenerateTokenValidationParameters(_signingKeys, _issuer);
return ValidateToken(jwtToken, validationParameters);
}
/// Method responsible for validating the token against the validation parameters. Key Rollover is
/// handled by refreshing the keys if SecurityTokenSignatureKeyNotFoundException is thrown.
private bool ValidateToken(string jwtToken, TokenValidationParameters validationParameters)
{
int count = 0;
bool result = false;
var tokenHandler = new JwtSecurityTokenHandler();
var claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out SecurityToken validatedToken);
result = (CheckTenantID(validatedToken));
return result;
}
/// Method responsible for sending proper Unauthorized reply if the token validation failed.
public void BeforeSendReply(ref Message reply, object correlationState)
{
var error = correlationState as WcfErrorResponseData;
if (error == null) return;
var responseProperty = new HttpResponseMessageProperty();
reply.Properties["httpResponse"] = responseProperty;
responseProperty.StatusCode = error.StatusCode;
var headers = error.Headers;
if (headers == null) return;
foreach (var t in headers)
{
responseProperty.Headers.Add(t.Key, t.Value);
}
}
}
NOTE - Please refer to this gist for complete Message Inspector code.
Custom Invoker - The job of custom Invoker is to stop the request flow to further stages of WCF Request processing pipeline if the Token is invalid. This is done by setting the Authorized flag as false in the current OperationContext (set in Message Inspector) and reading the same in Custom Invoker to stop the request flow.
class CustomInvoker : IOperationInvoker
{
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Check the value of the Authorized header added by Message Inspector
if (OperationContext.Current.IncomingMessageProperties.ContainsKey("Authorized"))
{
bool allow = (bool)OperationContext.Current.IncomingMessageProperties["Authorized"];
if (!allow)
{
outputs = null;
return null;
}
}
// Otherwise, go ahead and invoke the operation
return defaultInvoker.Invoke(instance, inputs, out outputs);
}
}
Here's the complete gist for Custom Invoker.
Now you need to inject the Message Inspector and the Custom Invoker to your WCF Pipeline using Endpoint Behavior Extension Element. Here are the gists for the class files to do this and a few other needed helper classes:
BearerTokenEndpointBehavior
BearerTokenExtensionElement
MyOperationBehavior
OpenIdConnectCachingSecurityTokenProvider
WcfErrorResponseData
The job is not done yet! You need to write a custom binding and expose a custom AAD endpoint in your web.config apart from adding the AAD Config Keys -
<!--List of AAD Settings-->
<appSettings>
<add key="AADAuthority" value="https://login.windows.net/<Your Tenant ID>"/>
<add key="AADAudience" value="your service side AAD App Client ID"/>
<add key="AllowedTenantIDs" value="abcd,efgh"/>
<add key="ValidateIssuer" value="true"/>
<add key="ValidateAudience" value="true"/>
<add key="ValidateIssuerSigningKey" value="true"/>
<add key="ValidateLifetime" value="true"/>
<add key="useV2" value="true"/>
<add key="MaxRetries" value="2"/>
</appSettings>
<bindings>
<wsHttpBinding>
<!--wsHttpBinding needs client side AAD Token-->
<binding name="wsHttpBindingCfgAAD" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" closeTimeout="00:30:00" openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00">
<readerQuotas maxDepth="26214400" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<!--Exposing a new baseAddress/wssecureAAD endpoint which will support AAD Token Validation-->
<service behaviorConfiguration="ServiceBehaviorCfg" name="Service">
<!--wshttp endpoint with client AAD Token based security-->
<endpoint address="wsSecureAAD" binding="wsHttpBinding" bindingConfiguration="wsHttpBindingCfgAAD" name="ServicewsHttpEndPointAAD" contract="ServiceContracts.IService" behaviorConfiguration="AADEnabledEndpointBehavior"/>
</service>
</services>
<behaviors>
<endpointBehaviors> <!--Injecting the Endpoint Behavior-->
<behavior name="AADEnabledEndpointBehavior">
<bearerTokenRequired/>
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions> <!--Linking the BearerTokenExtensionElement-->
<add name="bearerTokenRequired" type="TokenValidator.BearerTokenExtensionElement, TokenValidator"/>
</behaviorExtensions>
</extensions>
Your WCF service should now accept AAD Tokens on this custom AAD endpoint and your tenants will be able to consume the same by just changing the binding and endpoint from their side. Note that you will need to add the tenant's client ID in the allowedTenantIDs list in web.config so as to authorize the tenant to hit your service.
Final Note - Though I have implemented Microsoft's AAD Based Authentication, you should be able to reuse the whole code to implement any OAuth based Identity Provider's Token Validation. You just need to change the respective keys for AADAuthority in web.config.

You can implement Bearer token authentication
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Http;
[assembly: OwinStartup(typeof(ns.Startup))]
namespace ns
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
config.MessageHandlers.Add(new LogRequestAndResponseHandler());
}
Configure using of OAuthBearerAuthentication:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/TokenService"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(3),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
And finally set the identity claims
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, "Name"));
identity.AddClaim(new Claim(ClaimTypes.Sid, "Sid"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Role"));
context.Validated(identity);
}
catch (System.Exception ex)
{
context.SetError("Error....");
context.Response.Headers.Add("X-Challenge", new[] { ((int)HttpStatusCode.InternalServerError).ToString() });
}
}
}
}
}
It's the easiest solution and works like a charm!

Related

Alternate ways WCF Authentication with netTcpBinding binding

I'm writing a WCF server for an intranet application. I'm a newbie on WCF And have some queries on the Authentication. For this server the binding will netTcp, Its suggested to use netTcpBinding with Windows Authentication. But for my requirement I need a custom login where user will be validated with his own credentials not windows credentials.
I'm Planning to have a service contract implemented this way and validate the Login credentials
*[ServiceContract(SessionMode = SessionMode.Required)]
public interface Iservice
{
[OperationContract]
String Login(String username, String password);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession) ]
public class serviceclass : Iservice
{
String Login(String username, String password);
{
//Validate uname and password with DB.
if (validate)
return OperationContext.Current.SessionId;
else return String.Empty;
}
}*
Is this a good approach or is there a better approach to acheive this.
Please guide.
I would suggest to you to use the UserNamePasswordValidator for custom validation, it fits to your requirement.
You just need to inherit and implement validate from UserNamePasswordValidator class like this:
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
{
// This throws an informative fault to the client.
throw new FaultException("Unknown Username or Incorrect Password");
// When you do not want to throw an infomative fault to the client,
// throw the following exception.
// throw new SecurityTokenException("Unknown Username or Incorrect Password");
}
}
}
You can replace static username and password and validate it in your data storage.
Service configuration :
<serviceBehaviors>
<behavior name="CustomValidator">
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"MyAssembly.CustomUserNameValidator, MyAssembly"/>
</serviceCredentials>
</behavior>
<serviceBehaviors>
<netTcpBinding>
<binding name="tcpWithMessageSecurity">
<security mode="Message" >
<message clientCredentialType="UserName"/>
</security>
</binding>
</netTcpBinding>
For client side you can use Credentials to populate username and password.
proxy.ClientCredentials.UserName.UserName = "user";
proxy.ClientCredentials.UserName.Passsord = "password";

Can a WCF REST Service support both Basic and Windows authentication?

I have a self hosted REST WCF Windows Service. I've got Basic Authentication working for the service, but I would like to also support Windows Authentication for clients which support it. Would I have to have a separate endpoint on a different port?
UPDATE: I've gotten something close to working in WCF 4.0. Here is the code, the issue I'm having now is that I can only seem to get NTLM working, which requires the user to type in their credentials, which nullifies any benefits to using Windows Auth.
I'm still not sure how to get Windows Authentication working, without requiring the user to enter their password in a second time.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.IdentityModel.Selectors;
namespace BasicAndNegotiateAuth
{
class Program
{
static void Main(string[] args)
{
Uri newUri = new Uri(new Uri("http://localhost/"), "/");
WebServiceHost webHost = new WebServiceHost(typeof(HelloWorldService), newUri);
// TransportCredentialOnly means we can use http
WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic | HttpClientCredentialType.Ntlm;
ServiceEndpoint ep = webHost.AddServiceEndpoint(typeof(IHelloWorld), binding, newUri);
WebHttpBehavior wb = new WebHttpBehavior();
ep.EndpointBehaviors.Add(wb);
ep.Behaviors.Add(new WebHttpCors.CorsSupportBehavior());
//ServiceAuthenticationBehavior sab = null;
//sab = webHost.Description.Behaviors.Find<ServiceAuthenticationBehavior>();
//if (sab == null)
//{
// sab = new ServiceAuthenticationBehavior();
// sab.AuthenticationSchemes = AuthenticationSchemes.Basic | AuthenticationSchemes.IntegratedWindowsAuthentication;
// host.Description.Behaviors.Add(sab);
//}
webHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
webHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
webHost.Open();
Console.ReadLine();
}
}
public class CustomUserNameValidator: UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
int i = 1;
}
}
[ServiceContract]
public interface IHelloWorld
{
[System.ServiceModel.OperationContract]
[System.ServiceModel.Web.WebGet(
UriTemplate = "/",
ResponseFormat = WebMessageFormat.Json)]
string GetHello();
}
public class HelloWorldService : IHelloWorld
{
public string GetHello()
{
ServiceSecurityContext ssc = ServiceSecurityContext.Current;
return "Hello World";
}
}
}
In .NET 4.5, you can support multiple authentication schemes on a single endpoint in WCF.
Here is an example of how you would do it in code for a Self-Hosted Service:
ServiceAuthenticationBehavior sab = null;
sab = serviceHost.Description.Behaviors.Find<ServiceAuthenticationBehavior>();
if (sab == null)
{
sab = new ServiceAuthenticationBehavior();
sab.AuthenticationSchemes = AuthenticationSchemes.Basic |
AuthenticationSchemes.Negotiate | AuthenticationSchemes.Digest;
serviceHost.Description.Behaviors.Add(sab);
}
else
{
sab.AuthenticationSchemes = AuthenticationSchemes.Basic |
AuthenticationSchemes.Negotiate | AuthenticationSchemes.Digest;
}
Alternatively, you can set it up in your config file like this:
<behaviors>
<serviceBehaviors>
<behavior name="limitedAuthBehavior">
<serviceAuthenticationManager authenticationSchemes=
"Negotiate, Digest, Basic"/>
<!-- ... -->
</behavior>
</serviceBehaviors>
</behaviors>
Then specify InheritedFromHost in your binding settings like this:
<bindings>
<basicHttpBinding>
<binding name="secureBinding">
<security mode="Transport">
<transport clientCredentialType="InheritedFromHost" />
</security>
</binding>
</basicHttpBinding>
</bindings>
See this article on MSDN: Using Multiple Authentication Schemes with WCF.

WCF - Streaming file upload over http

I am trying to build a WCF service that will allow my WPF desktop clients to upload files to a server.
I adapted a code sample from The Code Project (WCF Streaming: Upload/Download Files Over HTTP) and I've looked at several SO posts as well, but can't seem to get this working.
When I execute the code, it fails with a null reference exception at the point that the server tries to read the stream that has been passed through the interface.
At this point, I am rather lost and don't know how to fix this up. Any suggestions are appreciated.
Code samples follow:
CustomerDocumentModel is the data element that I pass through the WCF interface with the stream to read the client side file:
[DataContract]
[KnownType(typeof(System.IO.FileStream))]
public class CustomerDocumentModel : IDisposable
{
public CustomerDocumentModel()
{
}
public CustomerDocumentModel(string documentName, string path)
{
DocumentName = documentName;
Path = path;
}
[DataMember]
public string DocumentName;
[DataMember]
public string Path;
[DataMember]
public System.IO.Stream FileByteStream;
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
IBillingService is the interface definition for my WCF service:
[ServiceContract]
public interface IBillingService
{
// other methods redacted...
[OperationContract]
void UploadCustomerDocument(CustomerDocumentModel model);
}
The class BillingService implements the WCF service:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class BillingService : IBillingService
{
// Other methods redacted ...
public void UploadCustomerDocument(CustomerDocumentModel model)
{
string path = HttpContext.Current.Server.MapPath(
String.Format("/Documents/{1}",
model.DocumentName));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
const int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int size = 0;
try
{
// The following Read() fails with a NullReferenceException
while ((size = model.FileByteStream.Read(buffer, 0, bufferSize)) > 0)
{
stream.Write(buffer, 0, size);
}
}
catch
{
throw;
}
finally
{
stream.Close();
model.FileByteStream.Close();
}
}
}
}
A few relevant bits from the web.config on my WCF web server:
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="2097151" useFullyQualifiedRedirectUrl="true" executionTimeout="360"/>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<bindings>
<basicHttpBinding>
<binding name="userHttps" transferMode="Streamed" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
The client is a WPF/MVVM app that creates a CustomerDocumentModel model, uses an OpenFileDialog to Open() the file stream and then passes the model to the UploadCustomerDocument method on WCF Service.
If I am missing any relevant details, please ask.
I know this rather very late reply for your question and I'm sure you must have resolved your problem as well. This could be helpful to someone else :-)
Use Messagecontract over Datacontract and only one MessageBodyMember with datatype Stream and rest all parameter are MessageHeader.
Here is the example:
[MessageContract]
public class CustomerDocumentModel : IDisposable
{
public CustomerDocumentModel(string documentName, string path)
{
DocumentName = documentName;
Path = path;
}
[MessageHeader]
public string DocumentName{get;set;}
[MessageHeader]
public string Path{get;set;}
[MessageBodyMember]
public System.IO.Stream FileByteStream{get;set;}
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
Note: Make sure your in your configuration transfer mode is StreamedResponse, also you may want to change the MessageEncoding to MTOM for better performance.
public void UploadCustomerDocument(CustomerDocumentModel model)
{
var filename = //your file name and path;
using (var fs = new FileStream(filename, FileMode.Create))
{
model.FileByteStream.CopyTo(fs);
}
}
Your data type is what is making the streaming fail. This is documented on MSDN here: http://msdn.microsoft.com/en-us/library/ms731913.aspx
The relevant passage is:
Restrictions on Streamed Transfers
Using the streamed transfer mode causes the run time to enforce
additional restrictions.
Operations that occur across a streamed transport can have a contract
with at most one input or output parameter. That parameter corresponds
to the entire body of the message and must be a Message, a derived
type of Stream, or an IXmlSerializable implementation. Having a return
value for an operation is equivalent to having an output parameter.
Some WCF features, such as reliable messaging, transactions, and SOAP
message-level security, rely on buffering messages for transmissions.
Using these features may reduce or eliminate the performance benefits
gained by using streaming. To secure a streamed transport, use
transport-level security only or use transport-level security plus
authentication-only message security.
SOAP headers are always buffered, even when the transfer mode is set
to streamed. The headers for a message must not exceed the size of the
MaxBufferSize transport quota. For more information about this
setting, see Transport Quotas.

ClaimsPrincipal is null when it reaches WCF Service

I am currently implementing a Federated Authentication solution using:
A passive STS for issuing tokens, a Website hosting a Silverlight application and WCF services for the Silverlight App.
So far I am able:
Get redirected to the STS
Login and get redirected to the Website
Display the claims on the website by accessing
HttpContext.Current.User.Identity as IClaimsIdentity;
on the web.config of the Website, I have added the two WIF modules needed (under IIS 7)
<modules runAllManagedModulesForAllRequests="true">
<add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler"/>
<add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler"/>
</modules>
I have also configured the Microsoft.IdentityModel section of the web.config to use my own implementation of ClaimsAuthenticationManager and ClaimsAthorizationManager.
<service name="Rem.Ria.PatientModule.Web.WebService.PatientService">
<claimsAuthenticationManager type ="Rem.Infrastructure.WIF.RemClaimsAuthenticationManager"/>
<claimsAuthorizationManager type ="Rem.Infrastructure.WIF.RemClaimsAuthorizationManager"/>
</service>
My ClaimsAuthenticationMAnager is simply setting the Thread.CurrentPrincipal is a valid Principal is provided.
class RemClaimsAuthenticationManager : ClaimsAuthenticationManager
{
public override IClaimsPrincipal Authenticate ( string resourceName, IClaimsPrincipal incomingPrincipal )
{
if ( incomingPrincipal.Identity.IsAuthenticated )
{
Thread.CurrentPrincipal = incomingPrincipal;
}
return incomingPrincipal;
}
}
}
The problem is that when my ClaimsAuthorizationManager is called, the context.Principal.Identity does not contain a valid Identity with Claims, and neither does the Thread.CurrentPrincipal.
Any ideas?
You don't need to set the Thread.CurrentPrincipal because the session module will do this for you. You will need to access it through the HttpContext.Current.User because the Thread.Principal is usually set on a different thread than the one accessing your service because it is two different modules in IIS. We have an example of this in our upcoming book that you can check out at our Codeplex Site.
HTH
The following sample code shows a sample class which inherits ClaimsAuthenticationManager. It just receives the incoming IClaimsPrincipal and passes through the claims, except the Name claim, which is modified. This does not set the CurrentPrincipal on the current thread, as in your example.
My test implementation is as follows:
public class CustomClaimsAuthenticationManager : ClaimsAuthenticationManager
{
public CustomClaimsAuthenticationManager()
{
}
public override IClaimsPrincipal Authenticate(string resourceName,
IClaimsPrincipal incomingPrincipal)
{
var outgoingIdentity = GetClaimsAsPassthrough(incomingPrincipal);
return outgoingIdentity;
}
private IClaimsPrincipal GetClaimsAsPassthrough(IClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return incomingPrincipal;
}
var ingoingClaims = incomingPrincipal.Identity as IClaimsIdentity;
ClaimsIdentity outgoingIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name, (incomingPrincipal.Identity.Name + "
a very cool guy"))
}, incomingPrincipal.Identity.AuthenticationType);
foreach (var claim in ingoingClaims.Claims.Where(
c => c.ClaimType != ClaimTypes.Name))
{
outgoingIdentity.Claims.Add(claim.Copy());
}
return new ClaimsPrincipal(new List<ClaimsIdentity> { outgoingIdentity });
}
}

When and where to set a custom IOperationInvoker?

I'm trying to extend WCF so that I can have a RESTful web service, in which, for each operation, I perform a verification of the HTTP Authorization header, whose value I use to call a Login() method.
After the login is done, I wish to invoke the operation's corresponding method checking if a security exception is thrown, in which case I'll reply with a custom "access denied" message" using the appropriate HTTP Status Code.
With this in mind, I thought implementing a IEndpointBehavior that applies an implementaion of IOperationInvoker to each operation (setting the DispatchOperation.Invoker property) would be a good idea.
I decided to implement an IOperationInvoker using the Decorator design pattern. My implementation would need another IOperationInvoker in it's constructor to which the method invocations would be delegated.
This is my IOperationInvokerImplementation:
public class BookSmarTkOperationInvoker : IOperationInvoker{
private readonly IOperationInvoker invoker;
public BookSmarTkOperationInvoker(IOperationInvoker decoratee)
{
this.invoker = decoratee;
}
public object[] AllocateInputs()
{
return this.invoker.AllocateInputs();
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
BeforeOperation(); // Where there's code to perform the login using WebOperationContext.Current
object o = null;
try
{
o = this.invoker.Invoke(instance, inputs, out outputs);
}
catch (Exception exception)
{
outputs = null;
return AfterFailedOperation(exception); // Return a custom access denied response
}
return o;
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
throw new Exception("The operation invoker is not asynchronous.");
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new Exception("The operation invoker is not asynchronous.");
}
public bool IsSynchronous
{
get
{
return false;
}
}
}
I decided to implement an IEndpointBehavior by extending the behavior I already needed (WebHttpBehavior) this way I only use one beavior. Here's the code I wrote:
public class BookSmarTkEndpointBehavior : WebHttpBehavior
{
public override void Validate(ServiceEndpoint endpoint)
{
base.Validate(endpoint);
}
public override void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
base.AddBindingParameters(endpoint, bindingParameters);
}
public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
{
IOperationInvoker defaultInvoker = operation.Invoker;
IOperationInvoker decoratorInvoker = new BookSmarTkOperationInvoker(defaultInvoker);
operation.Invoker = decoratorInvoker;
Console.Write("Before: " + ((object)defaultInvoker ?? "null"));
Console.WriteLine(" After: " + operation.Invoker);
}
}
public override void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
base.ApplyClientBehavior(endpoint, clientRuntime);
throw new Exception("The BookSmarTkEndointBehavior cannot be used in client endpoints.");
}
}
Now here's the problem:
Only the constructor is being invoked in the IOperationInvoker, none of the other methods are.
The decoratee IOperationInvoker (the one that's passed in the decorator's constructor) is null.
I'm guessing that maybe some other code from some other behavior is setting another IOperationInvoker in the OperationDispatcher.Invoker setting afterwards. Thus, overriding mine. This would clearly explain my situation.
What is happening and what should I do?
My service is self-hosted.
In case you need to see it, here is the configuration I have in the app.config file under system.serviceModel.
<services>
<service name="BookSmarTk.Web.Service.BookSmarTkService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/service"/>
</baseAddresses>
</host>
<endpoint
address=""
behaviorConfiguration="BookSmaTkEndpointBehavior"
binding="webHttpBinding"
bindingConfiguration="BookSmarTkBinding"
contract="BookSmarTk.Web.Service.BookSmarTkService">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name ="BookSmartkServiceBehavior">
<serviceDebug httpHelpPageEnabled="true" httpHelpPageUrl="/help.htm" includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="BookSmaTkEndpointBehavior">
<!--<webHttp/>-->
<bookSmarTkEndpointBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="BookSmarTkBinding">
</binding>
</webHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="bookSmarTkEndpointBehavior" type="BookSmarTk.Web.Service.BookSmarTkEndpointBehaviorElement, BookSmarTk.Web.Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
I you read this far I am deeply grateful towards you. Really, Thank You!
Instead of setting invokers at ApplyDispatchBehavior() method, you have to make an IOperationBehavior implementor:
public class MyOperationBehavior: IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Invoker = new BookSmarTkOperationInvoker(dispatchOperation.Invoker);
}
public void Validate(OperationDescription operationDescription)
{
}
}
and then at ApplyDispatchBehavior() you should set that behavior:
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
foreach (var operation in endpoint.Contract.Operations) {
if (operation.Behaviors.Contains(typeof(MyOperationBehavior)))
continue;
operation.Behaviors.Add(new MyOperationBehavior());
}
}
I am building something similar (I think - don't have the time to look through all your code), but have gone about it in a different way.
To achieve this I am using the following:
An IMessageInspector to read the incoming HTTP request message headers (in this case extracting a session Id from a cookie and retrieving a session object from a cache).
A combination of an IPrincipal and an IAuthorizationPolicy to implement my own custom authorization code (WCF will automatically invoke my code for requests to web service methods which have the attribute '[PrincipalPermission(SecurityAction.Demand, Role="somerole")]' set).
An IErrorHandler which catches any uncaught exceptions from the web service methods (including a permission denied exception thrown if authorization fails -- i.e. the IsRole method you implement in the IPrincipal returns false). If you catch the security denied exception you can then use WebOperationContext.Current to set the custom HTTP error codes for the response message.
A custom behavior (an IContractBehavior - but you can also use an EndPoint or Service behavior or whatever you want) which creates all the above at runtime and attaches them to the appropriate endpoints.
I know this is very old, but for me Alexey's answer
worked. However, only when the ApplyDispatchBehaviour method calls the base method. Like this:
public override void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
base.ApplyDispatchBehavior(endpoint, endpointDispatcher);
foreach (var operation in endpoint.Contract.Operations)
{
if (operation.Behaviors.Contains(typeof(AccessControlOperationBehaviour)))
continue;
operation.Behaviors.Add(new AccessControlOperationBehaviour());
}
}