Click here to Skip to main content
Click here to Skip to main content

WebService Authentication with UsernameToken in WSE 3.0

, 12 Jul 2007
Rate this:
Please Sign up or sign in to vote.
Passing a username and password as plain text

Introduction

When I started working with WSE3, I just wanted to test how to authenticate a web service call with a username and password. I read the Implementing Direct Authentication with UsernameToken in WSE 3.0 article and I found it was very useful will all of the issues regarding to security. Wait a minute. It was so complicated for a newbie like me. I didn't care how to secure the message between client and web services at this time yet. I just wanted to pass a username and password as plain text and the web service to do authentication for each method call. I will leave the message encrypting tasks between client and web service for another article.

Background

Here are some good articles:

I assume you've already installed Web Services Enhancements (WSE) 3.0.

Using the Code

UsernameToken with username and password will be attached to the SOAP header when the client makes a call to the web service. To do custom authentication at server side, you need to override the AuthenticateToken method of the UsernameTokenManager class. We will create a class library project called UsernameAssertionLibrary and add a class called ServiceUsernameTokenManager that inherits from UsernameTokenManager.

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Design;
using Microsoft.Web.Services3.Security;
using Microsoft.Web.Services3.Security.Tokens;

namespace UsernameAssertionLibrary
{
    public class ServiceUsernameTokenManager : UsernameTokenManager
    {
        /// <span class="code-SummaryComment"><summary />
</span>
        /// Constructs an instance of this security token manager.
        /// <span class="code-SummaryComment"></summary />
</span>
        public ServiceUsernameTokenManager()
        {
        }

        /// <span class="code-SummaryComment"><summary />
</span>
        /// Constructs an instance of this security token manager.
        /// <span class="code-SummaryComment"></summary />
</span>
        /// <param name="nodes" />An XmlNodeList containing XML elements from a configuration file.</param />
        public ServiceUsernameTokenManager(XmlNodeList nodes)
            : base(nodes)
        {
        }

        /// <span class="code-SummaryComment"><summary />
</span>
        /// Returns the password or password equivalent for 
        /// the username provided.
        /// <span class="code-SummaryComment"></summary />
</span>
        /// <param name="token" />The username token</param />
        /// <span class="code-SummaryComment"><returns />The password (or password equivalent) for the 
</span>
        /// username<span class="code-SummaryComment"></returns />
</span>
        protected override string AuthenticateToken(UsernameToken token)
        {
            string username = token.Username;

            // it's up to you where you will get a password for some user
            // you may:
            // 1) get the password hash from web.config or system registry
            //    if you are implementing per-server security
            // 2) get the password from the database or 
            // XML file for the given user name

            // for example purposes we just return a reversed value of 
            // username
            char[] ch = username.ToCharArray();
            Array.Reverse(ch);
            return new String(ch);
        }

    }
}

The AuthenticateToken method will be called by WSE3 to retrieve the password of token.Username. You can implement this method to get a password from database, XML file, etc. for the given username. WSE3 will check if the password returned by AuthenticationToken matches with the password in the SOAP header. If they don't match, an exception will be sent to the client.

Now that we have the UsernameAssertionLibrary DLL, we need to register it to our web service. Add an ASP.NET web service project to the solution. We need to add reference to the UsernameAssertionLibrary class library to the web service project or we can copy UsernameAssertionLibrary.dll to the bin directory of the web service folder.

To enable WSE 3.0, right click on the web service project and click WSE Settings 3.0.

  1. Under the General tab, check both Enable this project for Web Services Enhancements and Enable Microsoft Web Services Enhancement Soap Protocol Factory.
  2. Under the Security tab, in the Security Tokens Managers area, click Add.
    Fill in the fields as follows:
    1. Type: "UsernameAssertionLibrary.ServiceUsernameTokenManager, UsernameAssertionLibrary"
    2. Namespace: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
    3. LocalNode: "UsernameToken"
    4. Click OK. By doing this, you register our custom username token manager with WSE3.0.
  3. Under the Policy tab, check Enable Policy and click Add. Name your new application policy "ServerPolicy" and then click OK. In the WSE Security Settings Wizard window:
    1. Select Secure a service application and Username; then click Next
    2. Check Specify Username Token in code and click Next
    3. Select None (reply on transport protection), click Next and Finish
    Don't worry about the setting for Protection Order. We just make it work first before we can think how we should protect information sent via the network. There are several way you can secure your message -- for example, using SSL or WSE3.0 to encrypt the SOAP message -- but it is not in this very first look at WSE3.0.
  4. Under the Diagnostics tab, select Enable Message Trace. Enter InputTrace.webinfo into the Input text box and OutputTrace.webinfo into the Output text box. These are XML files you can use to trace SOAP messages sent from the client and service.

Now that we have wse3policyCache.config, take a look at this file:

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
    <extensions>
        <extension name="usernameOverTransportSecurity" type=
            "Microsoft.Web.Services3.Design.UsernameOverTransportAssertion,
            Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
        <extension name="requireActionHeader" type=
           "Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, 
           Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
           PublicKeyToken=31bf3856ad364e35" />
    </extensions>
    <policy name="ServerPolicy">
        <usernameOverTransportSecurity />
       <requireActionHeader />
    </policy>
</policies>

Open the web.config file; it looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <!-- Register WSE config section -->
        <section name="microsoft.web.services3" type=
            "Microsoft.Web.Services3.Configuration.WebServicesConfiguration,
            Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
    </configSections>
    <appSettings />
    <connectionStrings />
    <system.web>
        <!--<span class="code-comment"> Compilation settings --></span>
        <compilation debug="true">
            <assemblies>
                <add assembly="Microsoft.Web.Services3, Version=3.0.0.0, 
                     Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
                <add assembly="System.Security, Version=2.0.0.0, 
                     Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
                <add assembly="System.Configuration.Install, 
                     Version=2.0.0.0, Culture=neutral, 
                     PublicKeyToken=B03F5F7F11D50A3A" />
                <add assembly="System.Design, Version=2.0.0.0, 
                     Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A" />
            </assemblies>
        </compilation>
        <!--<span class="code-comment"> We don't require any authentication, because it will be 
            implemented on application layer --></span>
        <authentication mode="None" />
        <!--<span class="code-comment"> Web Services settings --></span>
        <webServices>
            <!--<span class="code-comment"> Enable WSE 3.0 --></span>
            <soapExtensionImporterTypes>
            <add type=
                "Microsoft.Web.Services3.Description.WseExtensionImporter, 
                Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
                PublicKeyToken=31bf3856ad364e35" />
            </soapExtensionImporterTypes>
            <soapServerProtocolFactory 
                type="Microsoft.Web.Services3.WseProtocolFactory, 
                Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
                PublicKeyToken=31bf3856ad364e35" />
        </webServices>
    </system.web>
    <!--<span class="code-comment"> WSE 3.0 config section --></span>
    <microsoft.web.services3>
        <diagnostics>
            <trace enabled="true" input="InputTrace.webinfo" 
                output="OutputTrace.webinfo" />
        </diagnostics>
        <security>
            <securityTokenManager>
                <add type=
                    "UsernameAssertionLibrary.ServiceUsernameTokenManager, 
                    UsernameAssertionLibrary" namespace=
                    "http://docs.oasis-open.org/wss/2004/01/
                    oasis-200401-wss-wssecurity-secext-1.0.xsd" 
                    localName="UsernameToken" />
            </securityTokenManager>
        </security>
        <policy fileName="wse3policyCache.config" />
    </microsoft.web.services3>
</configuration>

Now we need to code for our web service. Add reference to Microsoft.Web.Services3 to the project. Open Service.cs and copy the code:

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using Microsoft.Web.Services3;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[Microsoft.Web.Services3.Policy("ServerPolicy")]
public class Service : System.Web.Services.WebService
{
    public Service () 
   {

        //Uncomment the following line if using designed components 
        //InitializeComponent(); 
    }

    [WebMethod]
    public string HelloMyFriend() 
    {
        //Say hello to the guy that you know
        return "Hello " + 
            RequestSoapContext.Current.IdentityToken.Identity.Name;
    }   
}

The attribute [Microsoft.Web.Services3.Policy("ServerPolicy")] will register our ServerPolicy, which is configured in wse3policyCache.config to the service. We have a web method called HelloMyFriend() that returns a string Hello + the username in the token, which is passed in the SOAP header when the client makes a HelloMyFriend call. There is no code in the HelloMyFiend method to authenticate the username, but WSE will call AuthenticateToken() from our custom Username Token Manager to retrieve the password. It will then compare the password with the one in SOAP header sent from the client.

Now we will see how we can have username and password in the SOAP header when we make a service call. Add a Console Application project to our solution called TestWebService and add reference to Microsoft.Web.Services3. Before you can make a call to the web service, you need to have a proxy. You can either use Add Web Preference in Visual Studio or use WseWsdl3.exe to generate a proxy class. I used WseWsdl3.exe; the default is in Program Files\Microsoft WSE\v3.0\Tools. Here is the command to generate the ServiceProxy.cs class.

wsewsdl3 <a href="http://localhost:1515/WebService/Service.asmx?wsdl">http://localhost:1515/WebService/Service.asmx?wsdl</a> 
    /o:c:\ServiceProxy.cs /l:cs /type:webClient 

Add ServiceProxy.cs to our TestWebService project. Like the server side, the client also needs to have a policy. You need to configure the client to enable WSE3.0. To enable WSE 3.0, right click on the TestWebService project and click WSE Settings 3.0.

  1. Under the General tab, check Enable this project for Web Services Enhancements.
  2. Under the Policy tab, check Enable Policy and click Add. Name your new application policy "ClientPolicy" and click OK. In the WSE Security Settings Wizard window:
    1. Select Secure a client application and Username and click Next
    2. Check Specify Username Token in code and click Next
    3. Select None (reply on transport protection), click Next and Finish
  3. Under the Diagnostics tab, select Enable Message Trace. Enter InputTrace.webinfo into the Input text box and OutputTrace.webinfo into the Output text box.

We have wse3policyCache.config in the project look like this:

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
    <extensions>
        <extension name="usernameOverTransportSecurity" type=
            "Microsoft.Web.Services3.Design.UsernameOverTransportAssertion, 
            Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
        <extension name="requireActionHeader" type=
            "Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, 
            Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
    </extensions>
    <policy name="ClientPolicy">
        <usernameOverTransportSecurity />
        <requireActionHeader />
    </policy>
</policies>

The app.config file looks like this:

<configuration>
    <configsections>
        <section name="microsoft.web.services3" type=
            "Microsoft.Web.Services3.Configuration.WebServicesConfiguration,
            Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
    </configsections>
    <microsoft.web.services3>
        <diagnostics>
            <trace output="OutputTrace.webinfo" input="InputTrace.webinfo" 
                enabled="true" />
        </diagnostics>
        <policy filename="wse3policyCache.config" />
    </microsoft.web.services3>
</configuration>

Here is the client code:

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Web.Services3;
using Microsoft.Web.Services3.Design;
using Microsoft.Web.Services3.Security.Tokens;


namespace TestWebService
{
    class Program
    {
        static void Main(string[] args)
        {
            //init web service proxy 
            Service serviceProxy = new Service();

            //init UsernameToken, password is the reverted 
            //string of username, the same logic in AuthenticateToken
            //  of ServiceUsernameTokenManager class.
            UsernameToken token = new UsernameToken("admin", "nimda", 
                PasswordOption.SendPlainText);

            // Set the token onto the proxy
            serviceProxy.SetClientCredential(token);

            // Set the ClientPolicy onto the proxy
            serviceProxy.SetPolicy("ClientPolicy");

            //invoke the HelloMyFriend web service method
            try
            {
                Console.WriteLine(serviceProxy.HelloMyFriend());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Now try to run TestWebService. If you change the password to "nimda1" you will get an exception. You can open OutputTrace.webinfo to see the SOAP message. You might think that the username and password should not be seen. There are several way to secure messages, for example, using SSL (point to point) or WSE3.0 (end to end).

History

  • 6 July, 2007 -- Original version posted
  • 12 July, 2007 -- Updated
  • 4 September, 2007 -- Article edited and moved to the main CodeProject.com article base

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

DAT HAN

United States United States
No Biography provided

Comments and Discussions

 
QuestionHaving Problem PinmemberAlireza_136228-Apr-12 22:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 12 Jul 2007
Article Copyright 2007 by DAT HAN
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid