Gooddogs Development Blog

WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

Feb 13

Written by:
2/13/2011 4:47 PM  RssIcon

WCF is a highly configurable technology/framework from Microsoft. You can configure various deployment and security scenarious by simply editing your web.config file without have to make any code changes to your service contract or code. That is incredibly powerful. It is also incredibly complex. Most of the examples you will find if you search the internet for “WCF Security” will talk about transport and message security, federation, WIF, ACS, etc…. and you will spend hours and hours reading whitepapers and blog posts about all of the options for implementing security for your WCF service.

There is also not a single answer to the question of “how do I secure my WCF service?”. If you get 100 security experts in a room, you’ll get dozens of different answers, and that’s because of the extreme flexibility of WCF. You have many options. So, while reading this section, keep in mind that this is “my” suggestion for securing a WCF service. My requirements are that the security implementation secure both the SOAP and REST endpoints (ie: I don’t want to have to implement 2 different security schemes) and it should be easy to implement. (ie: I don’t want to have to write a bunch of security code in each and every exposed method). I believe the approach I am about to present meets those requirements extremely well. In fact, you’ll see that I really don’t have to write ANY security code in my methods at all!! I will just write my code, then apply a security layer “on top” of my OperationContracts. Buckle your seat belts!

If you remember, we’ve exposed two endpoints in our service, a soap based endpoint and a REST based endpoint. It is inherently easier (or at least relatively easier by many factors) to design and configure a security strategy for a soap based endpoint, most likely involving digital certificates or federated token security services. However, securing a REST endpoint is not quite so easy, since it is a highly open HTTP based protocol. I can execute a REST request simply using Internet Explorer. Since I’m not using a client-side proxy to make the service call, all of the security must be handled on the server side.

The one advantage we have in this example is that we are implementing our WCF service ‘within’ a DotNetNuke site. So our service is part of an existing ASP.NET-based web site that already has a robust security service. We can take advantage of that, and avoid the complexity of implementing security as we would have to had this been a stand-alone WCF service with it’s own base address.

Accessing a WCF service through a REST endpoint is a stateless exercise. It’s a request for a resource through a defined URL. The service receives the request, gathers the data, and sends the data back to the requestor. Connections are closed, as far as the service is concerned, that requestor may or may not ever make another request. In order to add some security to this process, we need a way for the requestor to identify themselves. We also want to make this service available to external clients, therefore we do not want to make our clients visit our DotNetNuke web site and log in. However, we do want to be able to use the DotNetNuke security framework to authenticate and authorize external clients.

The Approach:

The solution I am presenting will use a “Security Token”. We will require that before an external client can use our service, they must make a service call to a “login” method, passing their DotNetNuke User ID and Password. In response, if they are a valid DotNetNuke user on our portal, we will generate a “Security Token” that contains information about the user and send it back to them. Then in all subsequent requests, we will require that the user pass that token as part of the request so we can validate it and decide if they meet the security requirements for the method they are requesting.

Step 1. Authentication

The first thing a client will need to do is request an Access Token. They are, in effect, saying, I would like to call methods within your WCF service, here is my ID and Password. The WCF Service will take the ID and Password and use the DotNetNuke security service framework to authenticate the user and determine who they are. We will take the information we have discovered about the client and create a ‘security token’. The token will contain the UserID of the user.

OPTIONAL: You can decide what you want to store in your tokens, for example you may also want to store the IP address the request for a token was made from to add an additional layer of security, and make sure that requests using that token come from the same IP it was issued to. You could also store additional user-related data such as DisplayName, email address, etc .. whatever your requirements are.

Before passing the token back to the client, we generate a unique SecurityTokenID (we’ll use a Guid) and then we cache the token on the server so that in subsequent requests we can retrieve it from the cache and authenticate the user without having to hit the DotNetNuke security service again. (Fig 1). We add the token to the SecurityTokenCache with a Sliding Expiration timespan, meaning that once a token is “not used” for some period of time, it will expire.

image
Figure 1. Authentication : Request for Access

Once the client has received a token, they will pass the token along with all subsequent requests (Fig 2). The WCF service will take the TokenID, check the SecurityTokenCache and see if the token is still in the cache (ie: not expired). If the token is not found in the cache, the service will Throw a Security Exception. If the token is there, the token will be retrieved from the cache and now we know who is making the request.

image
Figure 2. Request for Data or Functionality include data + token

The Code : Login Function

We need a new OperationContract in our service, Login, that will allow a user to request service access. In our example, open the Interface file, IDNNService.vb and add a new OperationContract

  1:  <operationcontract()> 
</operationcontract()>
  2:  <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
  3:     UriTemplate:="login/{userid}/{password}")> _ 
  4:  Function Login(ByVal userid As String, _ 
  5:     ByVal password As String) As String
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

We define a function named Login, that will accept a User ID and Password, and expose a REST URI, login/userid/password

Let’s also declare a public class to define the structure of our Security Tokens. At the bottom of our IDDNService.vb file, add the following class. We’ll store the UserID and DisplayName in our tokens. Again, if you want to store more info in your tokens, just add more properties to your token class.

  1:  Public Class DNNServiceSecurityToken 
  2:     Public UserID As String 
  3:     Public UserName As String 
  4:  End Class

Now we need to write the implementation code for the Login() method. We will use the DotNetNuke security services to authenticate the user. Then we’ll construct a security token. Open the DNNService.vb file and add a new Imports statement at the top of the file

  1:  Imports DotNetNuke.Common.Utilities

Then add the following code.

  1: Public Function Login(ByVal userid As String, ByVal password As String) As String _ 
  2:    Implements IDNNService.Login 
  3: 
  4:    ' check userid and password using DotNetNuke security framework 
  5:    Dim loginStatus As New DotNetNuke.Security.Membership.UserLoginStatus 
  6:    Dim user As UserInfo = UserController.ValidateUser( _ 
  7:       0, userid, password, "", "", _ 
  8:       "0.0.0.0", loginStatus) 
  9: 
 10:     If user Is Nothing Then 
 11:        Throw New Exception("Access Request Denied. Invalid UserID _
 12: 	   and Password") 
 13:     End If 
 14:  
 15:     ' generate a unique Token Id 
 16:     Dim tokenId As Guid = Guid.NewGuid 
 17:  
 18:     ' create a security token 
 19:     Dim token As New DNNServiceSecurityToken() With { _ 
 20:        .UserID = user.UserID, _ 
 21:        .UserName = user.DisplayName 
 22:     } 
 23:  
 24:     ' cache the token using the built-in DotNetNuke caching framework 
 25:     ' prefix the tokenid with an identifier so that our token will be 'grouped' 
 26:     ' in the site cache, and set the sliding expiration to 5 minutes 
 27:     DataCache.SetCache( _ 
 28:        "DNNSecurityToken_" & tokenId.ToString(), token, _
 29: 	   New TimeSpan(0, 5, 0)) 
 30:  
 31:     ' return the tokenId 
 32:     Return tokenId.ToString() 
 33:  
 34: End Function

In Line 6. we use the DotNetNuke security service to authenticate the user. If the authentication fails, line 10, then our service will throw an exception back to the client indicating the User ID and Password is invalid.

On Line 15, we generate a Guid to use as our Token ID, create a new token on line 18 and add it to our cache using the DotNetNuke caching framework on line 26. Finally, we return the Token ID to the client on line 30, so they can use it in future requests.

How much are you loving being a DotNetNuke developer at this point … security, caching .. the DotNetNuke framework is extremely robust and you should make every effort when developing DotNetNuke solutions to take advantage of what it offers.

A quick explanation about the sliding cache:

We are using a sliding expiration for our cached Security Tokens, which means that if the token is used within the alloted time, the timeout value resets. So, for example, with a sliding cache timeout of 5 minutes, that means that if the next request using that token comes in within 5 minutes, the request will be processed and the timeout reset to 5 minutes again. As soon as 5 minutes passes without a subsequent request, the token will expire. That way, once a client is authenticated they can continue to use the token for as long as they are continuing to make requests.

Where are we so far?

We’ve talked a lot about security so far, but from a coding perspective all we done so far is define what our tokens looks like, added an OperationContract to allow a user to ‘login’, and wrote the code to use the DotNetNuke framework to authenticate the user and if valid, cache the token and return a token ID to the client.

Step 2. Authorization

Now we need to look at our OperationContracts and decide which ones will require security. For those that do, we need to add an additional incoming parameter, the security token. We have 4 OperationContracts at this point, GetMetrics(), GetUnapprovedRepositoryFiles(), ApproveRepositoryFile() and now Login(). Let’s require security on all of the operations except, of course, Login().

We update our IDNNService.vb file, adding {token} as a parameter. NOTE: That we add {token} to the end of the parameter list. With my approach it is important that {token} be the last parameter in each operation, the reason for that will be explained later. For now, the OperationContracts in your IDDNService.vb file should look like this.. (added code is highlighted)

  1: <operationcontract()> _ 
</operationcontract()>
  2: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
  3:    UriTemplate:="metrics/{token}")> _ 
  4: Function GetMetrics(ByVal token As String) As Metrics 
  5: 
  6: <operationcontract()> _ 
</operationcontract()>
  7: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
  8:    UriTemplate:="repository/get/{portalid}/{token}")> _ 
  9: Function GetUnapprovedRepositoryFiles(ByVal portalid As String, _
 10:    ByVal token As String) _ 
 11:    As List(Of RepositoryFile) 
 12:  
 13: <operationcontract()> _ 
</operationcontract()>
 14: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 15:    UriTemplate:="repository/approve/{itemid}/{token}")> _ 
 16: Function ApproveRepositoryFile(ByVal itemid As String, _
 17:    ByVal token As String) As Boolean 
 18:  
 19: <operationcontract()> _ 
</operationcontract()>
 20: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 21:    UriTemplate:="login/{userid}/{password}")> _ 
 22: Function Login(ByVal userid As String, ByVal _
 23:    password As String) As String
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

We then need to modify our Implementation code to add the token parameters. Open the DNNService.vb file and add the token parameter to the three functions.

  1: Public Function GetMetrics(ByVal token As String) As Metrics _ 
  2:    Implements IRepositoryService.GetMetrics 
  3: .... 
  4: 
  5: Public Function GetUnapprovedRepositoryFiles(ByVal portalid As String, _ 
  6:    ByVal token As String) As List(Of RepositoryFile) _ 
  7:    Implements IRepositoryService.GetUnapprovedRepositoryFiles 
  8: .... 
  9: 
 10: Public Function ApproveRepositoryFile(ByVal itemid As String, _ 
 11:    ByVal token As String) As Boolean _ 
 12:    Implements IRepositoryService.ApproveRepositoryFile 
 13: ....

We have now changed our OperationContracts for the Functions we want to secure, to require the token be passed as part of the parameters. Nothing to complicated so far .. pretty straight forward.

Here comes the WCF Magic!!!

Now, you might expect that the next step would be add code to each of those three functions, to take the incoming token id, validate that it is in the cache, get the user id from the token, check the roles and somehow determine whether the user can execute the function or not. You might even write a re-usable function to do that so you don’t have a bunch of repeated code. And you could certainly do that .. but one of my requirements was to make the code footprint as small as possible, and simplify the implementation.

Smile

WCF provide a number of ‘hooks’ into the messaging pipeline. We will take advantage of a couple of those hooks and Custom Attributes to implement our security without having to write ANY code in our functions. Let me say that again … you will see no more code changes to our functions. No reference to the token, no security checks, no exception handling .. all of our security will be applied through custom Attributes and WCF message handling. AND with NO config file settings either!

OperationBehaviors and Custom Attributes:

We will be defining a Custom Attribute that we can apply to our OperationContracts, and use that Attribute to specify required security roles for each function. A little sneak peak at what we will be implementing… Once we are done, an OperationContract in our IDNNService.vb file will look like this…

  1: <securitytokenvalidator("Administrators")> _ 
</securitytokenvalidator("
  2: <operationcontract()> _ 
</operationcontract()>
  3: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
  4:    UriTemplate:="metrics/{token}")> _ 
  5: Function GetMetrics(ByVal token As String) As Metrics
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

All we will need to do to implement security in an OperationContract is add the SecurityTokenValidator attribute and specify the required Security Role. In line 1 of the example above, we are restricting execution of the GetMetrics() function to members of the DotNetNuke Administrators role. We will not write any security code whatsoever in the GetMetrics() function, all the security checks will be performed by the SecurityTokenValidator. Only if the request comes with a valid token and the user is a member of the Administrators role, will the GetMetrics() function be executed.

To implement this strategy we need to add 2 new classes. SecurityTokenValidator() is a Custom Attribute that we can apply to our OperationContracts. We also need to write another class, SecurityTokenInspector(), which will implement the WCF IParameterInspector interface, and will allow us to pre-process the incoming parameters and that is where we do the actual token validation. Add a new class, SecurityTokenValidator() to your project and add the following code… (I’ve highlighted the only code you need to write)

 1: Imports System 
 2: Imports System.ServiceModel.Description 
 3: Imports System.ServiceModel.Channels 
 4: Imports System.ServiceModel.Dispatcher 
 5: 
 6: <attributeusage(attributetargets.all)> _ 
</attributeusage(attributetargets.all)>
 7:    Public Class SecurityTokenValidator 
 8: Inherits Attribute 
 9: Implements IOperationBehavior 
 10: 
 11: Public Roles As String 
 12: 
 13: Public Sub New(ByVal value As String) 
 14:    Roles = value 
 15: End Sub 
 16: 
 17: Public Sub AddBindingParameters( _ 
 18:    ByVal operationDescription As OperationDescription, _ 
 19:    ByVal bindingParameters As BindingParameterCollection) _ 
 20:    Implements IOperationBehavior.AddBindingParameters 
 21:    Return 
 22: End Sub 
 23: 
 24: Public Sub ApplyClientBehavior( _ 
 25:    ByVal operationDescription As OperationDescription, _ 
 26:    ByVal clientOperation As ClientOperation) _ 
 27:    Implements IOperationBehavior.ApplyClientBehavior 
 28:    Return 
 29: End Sub 
 30: 
 31: Public Sub ApplyDispatchBehavior( _ 
 32:    ByVal operationDescription As OperationDescription, _ 
 33:    ByVal dispatchOperation As DispatchOperation) _ 
 34:    Implements IOperationBehavior.ApplyDispatchBehavior 
 35: 
 36:    dispatchOperation.ParameterInspectors.Add(New SecurityTokenInspector(Roles)) 
 37:    Return 
 38: End Sub 
 39: 
 40: Public Sub Validate( _ 
 41:    ByVal operationDescription As OperationDescription) _ 
 42:    Implements IOperationBehavior.Validate 
 43:    Return 
 44: End Sub 
 45: 
 46: End Class

Line 6, along with line 8, establishes this class as a Custom Attribute that we will be able to use on our OperationContracts. Line 9 indicates that we our class will implement the IOperationBehavior interface which exposes the functions we need to ‘hook’ into our OperationContracts. We create a public property for storing the required Security Roles on line 11, and setup our class Constuctor to take a string and assign that string to our Roles property. If you remember the sneak peek, we will be attributing our OperationContracts like this…

 1: <securitytokenvalidator("Administrators")></securitytokenvalidator("

And the constructor, on line 14, will assign the string “Administrators” to the Roles property of our Custom Attribute.

Since we are only interested in processing incoming parameters, the only function we need to implement is the ApplyDispatchBehavior(). In that function, on line 36, we add our SecurityTokenInspector() to the service’s list of ParameterInspectors. This will cause WCF to call our custom Inspector when our service receives an incoming request. Notice how we pass our Validator “Roles” property to the Inspector. This will be used by the Inspector’s constructor and allow the Inspector code to know what roles are required.

Now all we have left to do is write our Custom Inspector.

Parameter Inspectors:

WCF provides a way for you to examine the parameters coming into and going out of each service call. This is provided by the IParameterInspector interface.

We need to write a new class and implement the IParameterInspector interface. Create a new file named SecurityTokenInspector.vb. (As referenced in line 36 in the SecurityTokenValidator() function above). In our case, we are only interested in examining incoming parameters, so we only need to implement the BeforeCall() function.

 1:  Imports System 
 2:  Imports System.ServiceModel.Dispatcher 
 3:  Imports DotNetNuke.Common.Utilities 
 4:  Imports DotNetNuke.Entities.Users 
 5: 
 6:  Public Class SecurityTokenInspector 
 7:     Implements IParameterInspector 
 8: 
 9:     Public Roles As String 
 10: 
 11:    Public Sub New(ByVal value As String) 
 12:       Roles = value 
 13:    End Sub 
 14: 
 15:    Public Function BeforeCall(ByVal operationName As String, _ 
 16:       ByVal inputs() As Object) As Object _ 
 17:       Implements IParameterInspector.BeforeCall 
 18: 
 19:       ' token will always be the last parameter 
 20:       Dim index As Integer = inputs.Length - 1 
 21:       Dim TokenId As String = inputs(index).ToString() 
 22: 
 23:       ' first make sure token exists 
 24:       Dim token As DNNServiceSecurityToken = 
              _DataCache.GetCache("DNNSecurityToken_" & TokenId) 
 25:       If token Is Nothing Then 
 26:          Throw New Exception( _ 
 27:          "Security Token Expired. Please request a new Token") 
 28:       End If 
 29: 
 30:       ' if token exists, check user roles 
 31:       Dim user As UserInfo = UserController.GetUserById(0, token.UserID) 
 32:       If Not user.IsInRole(Roles) Then 
 33:          Throw New Exception( _ 
 34:          "Access Denied. Role Membership Requirements not met") 
 35:          Return Nothing 
 36:       End If 
 37: 
 38:        Return Nothing 
 39: 
 40:    End Function 
 41: 
 42:    Public Sub AfterCall(ByVal operationName As String, _ 
 43:       ByVal outputs() As Object, ByVal returnValue As Object, _ 
 44:       ByVal correlationState As Object) _ 
 45:       Implements IParameterInspector.AfterCall 
 46:       Return 
 47:    End Sub 
 48: 
 49: End Class 

The BeforeCall function is fired prior to the service request being dispatched to your WCF service. It exposes the OperationContract name (operationName) and the incoming parameters (inputs) as an array of Objects. We will use that to grab the token id from the incoming request and validate it before the request ever gets to our service.

We Import some DotNetNuke namespaces that we will need to use the security and caching framework on lines 3 and 4. We then create a public property for Roles, just like we did in our Custom Validator, and write a constructor to take a string parameter and set the Roles property to the value passed. Remember, the Validator passed the Roles property to the Inspector when it was created.

Remember when I said it was important that the token always be the last parameter? You will now see why. The inputs array will contain the parameters in the order in which they are defined in the OperationContract. That means we can always find the token id in the parameter array by taking the last one. We calculate the number of parameters (line 20) and use that to grab the last parameter (line 21). We then check the Security Token Cache (line 24) and see if the token being passed in is still valid (line 25). If not, we throw a security exception (line 26) and the request will never get to our WCF service. This code provides the first level of security, a valid token.

Next we get the User ID out of the token and use the DotNetNuke security framework to check and see if that use is a member of the required Roles as passed into our Inspector during class construction (lines 31 and 32). If the user is not a member of the required roles, then we throw a security exception (line 33). If the token is valid and the user meets the security role requirements, then there’s nothing for us to do, we just return (line 38).

We’re Done .. Let’s review:

It seems like a lot, but really the actual code is quite small for what it is accomplishing.

Everything I’ve covered so far might seem a little intimidating, but if you break it all done, here is all we’ve done to fully implement DotNetNuke role-based security to our WCF service….

1. We started by adding a new OperationContract to allow a user to login and get a security token.

2. We modified our OperationContracts to include a token id as the last parameter.

3. We added a Custom Attribute that allowed us to specify the required security roles at the OperationContact.

4. And finally, we created a Parameter Inspector to intercept all incoming service requests and validate that the token was valid and the user met the security requirements.

You will notice… I made NO changes to the web.config and I made NO code changes to the source code of my functions. Security has been implemented at the “WCF” level, making configuration and implementation easy. All I need to do going forward to add security requirements to a service operation is add a SecurityTokenValidator attribute to my OperationContract. .. no coding!

Smile

Example in Action:

So let’s see how this all plays out with the example we’ve built over this series.

For this example, my OperationContracts look like this…

 1: <securitytokenvalidator("Administrators")> _ 
</securitytokenvalidator("
 2: <operationcontract()> _ 
</operationcontract()>
 3: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>metrics/{token}")> _ 
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 4: Function GetMetrics(ByVal token As String) As Metrics 
 5: 
 6: <securitytokenvalidator("Registered Users")> _ 
</securitytokenvalidator("
 7: <operationcontract()> _ 
</operationcontract()>
 8: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 9: UriTemplate:="repository/get/{portalid}/{token}")> _ 
 10: Function GetUnapprovedRepositoryFiles(ByVal portalid As String, _ 
 11: ByVal tokenId As String) As List(Of RepositoryFile) 
 12: 
 13: <securitytokenvalidator("Administrators")> _ 
</securitytokenvalidator("
 14: <operationcontract()> _ 
</operationcontract()>
 15: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 16: UriTemplate:="repository/approve/{itemid}/{token}")> _ 
 17: Function ApproveRepositoryFile( _ 
 18: ByVal itemid As String, ByVal token As String) As Boolean 
 19: 
 20: <operationcontract()> _ 
</operationcontract()>
 21: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 22: UriTemplate:="login/{userid}/{password}")> _ 
 23: Function Login( _ 
 24: ByVal userid As String, _ 
 25: ByVal password As String) As String
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

I added SecurityTokenValidator attributes to the GetMetrics (line 1), GetUnapprovedRepositoryFiles (line 6) and ApproveRepositoryFile (line 13) Functions. The user must be a member of the “Administrators” role to execute GetMetrics() and ApproveRepositoryFile(), but any member of “Registered Users” can see the list of Unapproved files.

Let’s test this using the REST API and Internet Explorer.

I open my Browser and browse to the Login URI to get a security token

image

In this case I pass in demoUser/demoUser as the ID and password, and in my installation that is a Registered User who is not a member of the Administrator role. I get back a token id (Guid)

Next test .. invalid User ID and Password

Let’s try and get a security token with an invalid user id and password. This time we pass in an invalid user id and password combination.

image

Good. Our login method attempted to validate the user and threw an exception “Access Request Denied. Invalid User ID and Password”

Next test .. invalid token

Let’s try and execute a request for Site Metrics, but pass a bad value for the token. We browse to the REST URI for metrics, but pass a bunch of 000’s for the token ID.

image

So, what happened here, is that our request was intercepted by our Parameter Inspector, the token id was pulled from the parameter list and the Security Token Cache was checked. That token id was not found in the cache, so our Parameter Inspector threw an exception “Security Token Expired”. EXACTLY what we hoped would happen!

Next test .. Security Roles

Now, let’s test with a valid token, but with a token for a user who does not meet the security role requirements. We have a token for demoUser, who is a member of the “Registered Users” role, but not an “Administrator”. Based on our OperationContracts, he should be able to get a list of unapproved repository items, but not site metrics. Let’s test that out…

Let’s try and execute a request for site metrics. I execute another login request to get a new token (the token I got in the first test expired while I was typing). Using the token id I get back from the login function, I browse to the REST URI for GetMetrics()

image

Wow, this is really working well!.. You see in the REST URI I requested site metrics and passed a token id (Guid). The Parameter Inspector intercepted the request, pulled the token id from the parameter list, checked the Security Token Cache, found the token, pulled the User ID from the token and used the DotNetNuke Security framework to get a list of roles the user belongs to.. It then checked to see if the user was in the roles specified in the Custom Attribute, and in this case where we specified the user has to be in the “Administrator” role, the role check failed and our Inspector threw and exception “Access Denied. Role Membership Requirements not met”. YES!

And the cool thing is .. here is the GetMetrics() function. Other than the inclusion of token as a parameter, there is no security code anywhere to be seen, yet we’ve implement DotNetNuke role-based security.

 1:  Public Function GetMetrics(ByVal token As String) As Metrics _ 
 2:    Implements IRepositoryService.GetMetrics 
 3: 
 4:    Dim results As New Metrics 
 5: 
 6:    Dim logController As New _
 7:       DotNetNuke.Services.Log.SiteLog.SiteLogController 
 8: 
 9:    Dim StartDate As String = Date.Today().ToShortDateString() 
 10:   Dim EndDate As Date = Date.Now() 
 11: 
 12:   Dim reader As IDataReader = logController.GetSiteLog(0, _ 
 13:      "localhost/dnn531", 1, StartDate, EndDate) 
 14:   reader.Read() 
 15: 
 16:   results.Views = Int32.Parse(reader("Views")) 
 17:   results.Visitors = Int32.Parse(reader("Visitors")) 
 18:   results.Users = reader("Users") 
 19: 
 20:   Return results 
 21: 
 22: End Function

Final test .. Valid Token, Security Role requirements met

So, in the final test, we’ll login as a user who is a member of the Administrators role, getting a security token, and then request site metrics. If everything is working, we should get data back this time.

image

Smile

All our tests passed!!

We now have a fully secured DotNetNuke WCF service that uses the DotNetNuke security framework to enable role-based OperationContract-level security. And meets our requirements of easy to implement, easy to configure.

If I wanted to change the security requirements for the GetMetrics() function and allow any Registered User to see the site metrics, I only need to change my IDNNService.vb interface file and edit the SecurityTokenValidator attribute for my GetMetrics OperationContract from

 1: <securitytokenvalidator("Administrators")> _ 
</securitytokenvalidator("
 2: <operationcontract()> _ 
</operationcontract()>
 3: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 4:    UriTemplate:="metrics/{tokenId}")> _ 
 5: Function GetMetrics(ByVal tokenId As String) As Metrics
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

to this…

 1: <securitytokenvalidator("Registered Users")> _ 
</securitytokenvalidator("
 2: <operationcontract()> _ 
</operationcontract()>
 3: <webget(responseformat:webget(responseformat:=webmessageformat.xml,>
 4:    UriTemplate:="metrics/{tokenId}")> _ 
 5: Function GetMetrics(ByVal tokenId As String) As Metrics
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

and now any member of the Registered Users role with a valid token would be able to call the service and get site metrics.

Summary:

Security is a complicated and very important part of any web-based application. I hope that this section provides you with an easy to implement solution that allows you to safely and securely take advantage of WCF services as part of your DotNetNuke module development.

This ends my series on using WCF with DotNetNuke. I hope you found the series useful and informative. Even if you are not using DotNetNuke, hopefully you can take the WCF concepts I’ve discusssed and apply them to your WCF projects.

Thanks for reading!

</webget(responseformat:webget(responseformat:=webmessageformat.xml,>
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>
</webget(responseformat:webget(responseformat:=webmessageformat.xml,>

Tags:
Categories:

10 comment(s) so far...


Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

Thank you for this article.

I am trying to setup a similar WCF service using the UserController.ValidateUser function. I am getting a NullReferenceException which I believe is related to the web.config. Can you post the web.config file you used for your WCF service?

Regards, Mike..

By Mike on   4/5/2011 12:33 PM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

@Mike, the web.config is listed and explained in Part #4 of this series

By Steve Fabian on   4/5/2011 2:08 PM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

@Steve, I should have expanded on my question. My service is configured and working, but my calls to UserController.ValidateUser result in a NullReferenceException. I don't see any database connection information in the WCF service web.config. How does the service know which DNN database to validate the user against?

By Mike on   4/8/2011 3:32 PM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

Thank you for this great article.

Could you please share example module (source code) for better understanding !? It will be useful for dnn community.

Best regards,
Evgeny.

By moefree on   4/15/2011 6:51 AM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service


I am trying to implement a similar service, but the BeforeCall() method will not execute :(. What am I missing?




Imports System.IO
Imports System.Web
Imports System.Web.Services
Imports System.Xml
Imports System.Xml.Serialization

Imports System.Web.Services.Protocols
Imports System.Web.Script.Services
Imports DotNetNuke.Entities.Users
Imports System.DirectoryServices
Imports System.Collections.Generic

Imports DotNetNuke.Common.Utilities
Imports DotNetNuke.Data

Imports System

Imports System.ServiceModel.Description
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Dispatcher
Imports System.Reflection


Namespace MyWebService

_
Public Class SecurityTokenValidator
Inherits Attribute
Implements IOperationBehavior

Public Roles As String

Public Sub New(ByVal value As String)
Roles = value
End Sub

Public Shared Sub yVal Msg As String)
Dim objEventLog As New DotNetNuke.Services.Log.EventLog.EventLogController
objEventLog.AddLog("Debug:", Msg , PortalController.GetCurrentPortalSettings(), 0,DotNetNuke.Services.Log.EventLog.EventLogController.EventLogType.ADMIN_ALERT)
End Sub

Public Sub AddBindingParameters( _
ByVal operationDescription As OperationDescription, _
ByVal bindingParameters As BindingParameterCollection) _
Implements IOperationBehavior.AddBindingParameters
Return
End Sub

Public Sub ApplyClientBehavior( _
ByVal operationDescription As OperationDescription, _
ByVal clientOperation As ClientOperation) _
Implements IOperationBehavior.ApplyClientBehavior
Return
End Sub

Public Sub ApplyDispatchBehavior( _
ByVal operationDescription As OperationDescription, _
ByVal dispatchOperation As DispatchOperation) _
Implements IOperationBehavior.ApplyDispatchBehavior
ApplyDispatchBehavior")
dispatchOperation.ParameterInspectors.Add(New SecurityTokenInspector(Roles))
Return
End Sub

Public Sub Validate( _
ByVal operationDescription As OperationDescription) _
Implements IOperationBehavior.Validate
Return
End Sub
End Class

Public Class SecurityTokenInspector
Implements IParameterInspector

Public Roles As String
Public Sub New(ByVal value As String)
Roles = value
End Sub

Public Shared Sub yVal Msg As String)
Dim objEventLog As New DotNetNuke.Services.Log.EventLog.EventLogController
objEventLog.AddLog("Debug:", Msg , PortalController.GetCurrentPortalSettings(), 0,DotNetNuke.Services.Log.EventLog.EventLogController.EventLogType.ADMIN_ALERT)
End Sub

Public Function BeforeCall(ByVal operationName As String, _
ByVal inputs() As Object) As Object _
Implements IParameterInspector.BeforeCall

Throw New Exception( _
"Access Denied. Role Membership Requirements not met")

SecurityTokenInspector.BeforeCall")

Dim user As UserInfo = UserController.GetCurrentUserInfo

If user Is Nothing Then
Throw New Exception( _
"Security Token Expired. Please request a new Token")
End If

If Not user.IsInRole(Roles) Then
Throw New Exception( _
"Access Denied. Role Membership Requirements not met")
Return Nothing
End If
Return Nothing
End Function

Public Sub AfterCall(ByVal operationName As String, _
ByVal outputs() As Object, ByVal returnValue As Object, _
ByVal correlationState As Object) _
Implements IParameterInspector.AfterCall
Return
End Sub

End Class





_
_
_
Public Class WebService
Inherits System.Web.Services.WebService


_
_
Public Function GetServerTime() As String
Dim serverTime As String = [String].Format("The current time is {0}.", DateTime.Now)
SecurityTokenValidator. erverTime)
Return serverTime
End Function

End Class

End Namespace

By moefree on   6/21/2011 7:30 AM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

I was wondering if you have had a look at iWebCF

iwebcf.codeplex.com/

I was wondering how would I would go about modifying iWebCF so that it could serve restful services as per your tutorial

Your help on this matter would be much appreciated.

Thanks and Kind Regards,

vnetonline

By Vineet belani on   7/10/2011 8:45 PM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

How can y change security group name from some Database table:



I want that "Administrators" or other groups can be define in Database and not directly in code..

By Jernej on   8/29/2011 5:43 AM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

SecurityTokenValidator("Administrator")

By Jernej on   8/29/2011 5:47 AM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

Terrific series and a huge help for a project I just worked on to allow validation of users from another site against the DNN database. Thanks so much for publishing this work!

Have you ever had the need to return serialized JSON however?

By Steven Webster on   4/9/2012 2:53 PM
Gravatar

Re: WCF Series: Part #8 Using DotNetNuke Security Framework to secure your WCF Service

Working on a way to raise the security token expired and role check validation to I can return an error code to the client as JSON. My understanding so far is that raising the exception in BeforeCall it gets intercepted and presented as a 400 exception error to the client. In my specific case we own both the client and the service and would like to respond back with something like this:

"{\"ErrorCode\":\"token_expired\"}"

or this:

"{\"ErrorCode\":\"invalid_role\"}"

I'm knee deep in WCF books and online references but wondered if you had a suggestion (we based our services on your example)

By Steven Webster on   6/15/2012 1:01 PM
Search
Privacy Statement | Terms Of Use | Copyright 2007-2009 by Gooddogs.com