Basic HTTP Authentication in a Web Service

Although you should almost never use basic auth to authenticate users, here is some information on how to do it in a Java-based web service.

Server side

JBoss

This refers to JBoss 4.2.2-ga

If you need to set up basic authentication with JBoss, you will have to configure JBoss to authenticate users for a particular "Security Domain".

Replace "default" here with your server name.

In server/default/conf/login-config.xml you define the policy for the new Security Domain. Let's call the SD "foobar".

<application-policy name="foobar">
  <authentication>
    <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag = "required">
      <module-option name="usersProperties">props/foobar-users.properties</module-option>
      <module-option name="rolesProperties">props/foobar-roles.properties</module-option>
    </login-module>
  </authentication>
</application-policy>

This tells JBoss to use the simple "users/roles" module for the domain "foobar", and that usernames, passwords and roles are defined in the files foobar-users.properties and foobar-roles.properties in server/default/conf/props.

foobar-users.properties

user1=password1
user2=password2

foobar-roles.properties

user1=role1,role2
user2=role2

After making a change like this, you will likely have to restart JBoss.

Tomcat

In Tomcat, you define "Realms". An example of a configuration using a JDBC connector and authenticating against users/roles defined in database tables could look like this:

	<Realm className="org.apache.catalina.realm.DataSourceRealm"
		debug="99" dataSourceName="jdbc/FoobarDB" userTable="person"
		userNameCol="person" userCredCol="password"
		userRoleTable="person_roles" roleNameCol="role_name" />

This would be located in WebContent/META-INF/context.xml.

Webapp configuration

All you need here is two files, both located in WEB-INF:

In your web.xml you tell JBoss that you want to use Basic Authentication:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>The Foobar Service</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>roll1</role-name>
    </auth-constraint>
  </security-constraint>
  <login-config>
    <auth-method>BASIC</auth-method>
  </login-config>
  <security-role>
  <description>Known users of the Foobar service</description>
  <role-name>roll1</role-name>
</security-role>

For JBoss, you also need a jboss-web.xlm to define which security domain the application belongs to. This is the connection to the server configuration described above:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
  <security-domain>java:/jaas/foobar</security-domain>
</jboss-web>

More information

In the implementation class for the web service, you can retrieve the username and the remote IP address like this:

MessageContext msgCtx = MessageContext.getCurrentContext();
String userName=msgCtx.getUsername();
String remoteAddress=null;
HttpServletRequest httpServletRequest = (HttpServletRequest)msgCtx.getProperty("transport.http.servletRequest");
if (httpServletRequest != null) remoteAddress=httpServletRequest.getRemoteAddr();

Client side

Java

In a Java-based client you first need to get hold of a javax.xml.rpc.Stub for the service proxy you use to call the Web Service. If you used the Eclipse/WTP wizard to create the client proxy, you can get hold of the stub like this ("iface" is the proxy and "MyService" is the name of the service):

  javax.xml.rpc.Stub s=((javax.xml.rpc.Stub)iface.getMyService());

If you use the Axis WSDL2Java command to build your service proxy, it looks just a little bit different. You start by creating a ServiceLocator and from that you get a "service stub" that can be cast directly to java.xml.rpc.Stub:

  javax.xml.rpc.Stub s=(javax.xml.rpc.Stub)myServiceStub;

Once you have your Stub you can just go on and set the username and password, and the rest will be handled automagically:

  s._setProperty(javax.xml.rpc.Stub.USERNAME_PROPERTY, "user1");
  s._setProperty(javax.xml.rpc.Stub.PASSWORD_PROPERTY, "password1");

C#

C# doesn't support HTTP 1.1 correctly, so you will have to do quite a bit to get it to work properly. First, you have to force it to run HTTP 1.0 instead, and you do that by subclassing your proxy class:

class OMyService : My.MyService
{
    protected override WebRequest GetWebRequest(Uri uri)
    {
        HttpWebRequest webRequest = (HttpWebRequest)base.GetWebRequest(uri);
        webRequest.ProtocolVersion = HttpVersion.Version10;
        return webRequest;
    }
}

Because of the way C# is designed, you can put this code in your main program file if you want, and within the same namespace you use for the main program.

Once this is done, you can just request basic authentication, just after instantiating your service proxy (iface in this example) - which of course has to be created from the new subclass, not the original proxy class!

iface.Credentials = new NetworkCredential("user1", "password1");

But wait! You didn't really think it would be that easy, did you?

While the .NET code can authenticate at this point, it blatantly refuses to do so until it's tried without once and been rejected with a 401 response. There is a really lovely property that you will surely want to use:

iface.PreAuthenticate = true;

Yeah, right. This parameter actually does what you want, but not on the first call! It's totally incomprehensible why this is so. In our case, this property is pointless - we know that we will need to authenticate, so we want to avoid that extra call that each and every RPC call would generate.

We can make use of the subclass that we were forced to create, instead. We simply build our own Authorization header and include it in the call. Here's the same code as before, slightly modified.

When we do this, we don't have to change the HTTP version like we did earlier.

protected override WebRequest GetWebRequest(Uri uri)
{
  HttpWebRequest webRequest = (HttpWebRequest)base.GetWebRequest(uri);
  NetworkCredential credentials = Credentials as NetworkCredential;
  if (credentials != null)
  {
    string authInfo =
      ((credentials.Domain != null) && (credentials.Domain.Length > 0) ?
      credentials.Domain + @"\" : string.Empty) +
      credentials.UserName + ":" + credentials.Password;
    authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
    webRequest.Headers["Authorization"] = "Basic " + authInfo;
  }
  return webRequest;
}

Visual C++

It's very likely possible here too, but it seems virtually impossible to figure out how. Remember that the above incompatibility issue probably applies to C++ too.

One way of doing the authentication, if you have to, is to manually add the Authorization header in the call to SendRequest, in the auto-generated header file for the Web Reference.

Remember to save copies of all the changes you make in this file! The file is auto-generated and will be regenerated in some cases, which will remove your changes without a trace.

Find the Web Service methods you are going to call, and then look up the call to SendRequest. The "SOAPAction" header is already there as a parameter:

  __atlsoap_hr = SendRequest(_T("SOAPAction: \"\"\r\n"));

Concatenate the username and password with a colon (":") inbetween, and Base64-encode the result. Then do this:

__atlsoap_hr = SendRequest(_T("Authorization: Basic VXNlcjE6TPZzZW5vcmQx\r\nSOAPAction: \"\"\r\n"));

Another way would be to overload SendRequest and add a method to set "credentials". In the header file for the web reference, you first add two private members:

CString username;
CString password;

Then you add two public methods, one of which overloads SendRequest(LPCTSTR):

HRESULT SendRequest(LPCTSTR szAction) {
  if (!username.IsEmpty() && !password.IsEmpty()) {
    CString extraheaders(szAction);
    extraheaders += _T("Authorization: Basic ");
    CString authStr = username + _T(":") + password;
    char authStrBuf[128], b64StrBuf[128];
    BOOL tainted=false;
    int n=WideCharToMultiByte(CP_ACP, 0, authStr, authStr.GetLength(),
                                                      authStrBuf, sizeof(authStrBuf), ".", &tainted);
    int olen=sizeof(b64StrBuf)-1;
    Base64Encode((BYTE *)authStrBuf, n, b64StrBuf, &olen);
    b64StrBuf[olen]='\0';
    CString b64str = b64StrBuf;
    extraheaders += b64str + _T("\r\n");
    szAction=_wcsdup(extraheaders);
  }
  return TClient::SendRequest(szAction);
}
 
void SetCredentials(LPCTSTR uname, LPCTSTR pwd) {
  username=uname;
  password=pwd;
}

Then you use SetCredentials(LPCTSTR, LPCTSTR) to set username and password and to make the proxy automatically send an Authorization header.

iface.SetCredentials(_T("User1"), _T("Password1"));

These methods work just as well in Windows CE/Windows Mobile.