Introduction
WebRequest
/Response
class supports almost all of authentication methods used in the internet, such as Negotiate (SPNEGO), Kerberos, NTLM, Digest, and Basic, and there is no need to code authentication logics by ourselves, in the usual case. .NET also allows us to add custom authentication modules to the WebRequest
/Response
authentication layer.
This article describes the implementation of a custom authorization module of WSSE, which most of AtomAPI
services use for authorization method. The module implements IAuthenticationModule
interfaces and therefore it is compatible with WebRequest
/Response
authentication layer. You can support WSSE authentication without changing your HTTP client code.
WSSE Authentication
Originally, the WSSE authentication was made for SOAP web services. However, the Username Token algorithm of WSSE can be easily adapted to the HTTP authentication and now it is widely used in AtomAPI
web services. The detail of WSSE authentication for AtomAPI
is described in here.
Note: The AtomAPI
has been updated to AtomPP
and the draft mentions only Basic authentication. The WSSE authentication may be out of style in the near future.
Implementation
The below logics are necessary for WSSE authentication.
- Base64 encoding
- sha1 hash algorithm
- ISO8601 format
- Secure Nonce creation
Since .NET Framework has all these codes in the class library, the authentication can be written in a very simple way, as follows:
if (credentials == null) return null;
NetworkCredential credential = credentials.GetCredential(request.RequestUri, "WSSE");
if (credential == null) return null;
ICredentialPolicy credentialpolicy = AuthenticationManager.CredentialPolicy;
if ((credentialpolicy != null) && (!credentialpolicy.ShouldSendCredential
(request.RequestUri, request, credential, this))) return null;
byte[] nonce = CreateNonce();
string createtime = DateTime.Now.ToUniversalTime().ToString
(DateTimeFormatInfo.InvariantInfo.SortableDateTimePattern) + "Z";
byte[] digestbuf = new byte[nonce.Length + Encoding.UTF8.GetByteCount
(createtime + credential.Password)];
nonce.CopyTo(digestbuf, 0);
Encoding.UTF8.GetBytes(createtime + credential.Password).CopyTo(digestbuf, nonce.Length);
SHA1 sha1 = SHA1.Create();
string digest = Convert.ToBase64String(sha1.ComputeHash(digestbuf));
request.Headers.Add("X-WSSE", string.Join("", new string[]
{ "UsernameToken ", "Username=\"", credential.UserName, "\", ",
"PasswordDigest=\"", digest, "\", ", "Nonce=\"",
Convert.ToBase64String(nonce), "\", ", "Created=\"", createtime, "\"" }));
It should be noted that you should make the Nonce with great care. System.Random
class is not secure enough for the Nonce. Use RNGCryptoServiceProvider
class for making a random number.
Custom Authentication Module
In the .NET Framework, authentication processes of WebRequest
/Response
are managed by AuthenticationManager
class and IAuthenticationModule
interface. Each of the authentication logics are described in each authentication classes which implement IAuthenticationModule
. For example, System.Net.NegotiateClient
class corresponds to SPNEGO authentication and System.Net.BasicClient
class corresponds to Basic authentication. The AuthenticationManager
class holds IAuthenticationModule
list, in which the default authentication modules are registered during the application startup.
When the server responses web request and requires the authentication, AuthenticationManager
calls IAuthenticationModule.Authenticate
function of the inner IAuthenticationModule
list with the corresponding WebRequest
, challenge string, and credentials. The function is called in the order in which they registered. If called IAuthenticateModule
cannot perform authentication, it returns nothing and AuthenticationManager
calls next module. If module can perform authentication, it returns an instance of Authorization
class which contains the information of authentication. In the case of HTTP, the challenge string comes from the HTTP "WWW-Authenticate" header and the HTTP "Authorization" header is made by the returned Authorization
object.
AuthenticationManager
also supports Preauthentication
. If the request URI has the same authority (schema, host, and port) as the URI requested before, AuthenticationManager
calls IAuthenticationModule.Preauthenticate
functions before the request is sent and determines the authentication string. The preauthenticate process prevents unnecessary round trip of the request and response.
The implementation of IAuthenticationModule
is simple. You should implement only two properties and two functions.
Although the IAuthenticationModule
members are called from not only one thread, you don't need to implements these members in the thread safe manner. The calls are already synchronized.
AuthenticationType Property
Name of authenticationtype. This time, it is "WSSE
".
public string AuthenticationType {
get {
return "WSSE";
}
}
CanPreAuthenticate Property
True
means that the module supports pre-authentication. WSSE
authentication can support pre-authentication.
public bool CanPreAuthenticate {
get {
return true;
}
}
Authenticate / PreAuthenticate Function
These functions are called by AuthenticationManager
when the WebRequest
needs authentication information. The implementations of these functions are almost the same. The only difference is that PreAuthenticate
function doesn't have challenge argument because it calls before sending a request.
You should implement these methods as below:
- Confirm "
challenge
" argument contains corresponding signature ("WSSE
"). It's not so simple because there may be quoted messages in the challenge string and it should be ignored.
if (!ContainsSignatureInChallenge(challenge, Signature)) return null;
- Get
NetworkCredential
corresponds to the request URI from "credentials" arguments.
if (credentials == null) return null;
NetworkCredential credential =
credentials.GetCredential(request.RequestUri, AuthenticationType );
if (credential == null) return null;
- Call
ShouldSendCredential.CredentialPolicy
to check whether credential should be sent or not.
ICredentialPolicy credentialpolicy = AuthenticationManager.CredentialPolicy;
if ((credentialpolicy != null) && (!credentialpolicy.ShouldSendCredential
(request.RequestUri, request, credential, this))) return null;
- Change
WebRequest
object if necessary. WSSE
authentication needs "X-WSSE
" header that contains Username, Nonce, Create Time, and Password Digest.
request.Headers.Add("X-WSSE", ..... );
- Return new
Authorization
object which contains authorization information for "Authorization
" header. In the case of WSSE
, "Authorization
" header doesn't contain any secure information.
return new Authorization("WSSE profile=\"UsernameToken\"", true);
How to Enable Custom Authentication Module
To use Custom Authentication Module, you should register it to AuthenticationManager
. Usually, call AuthenticationManager.Register
in the initialization code of application.
System.Net.AuthenticationManager.Register(New WSSEClient)
You can use application configuration file also.
<configuration>
<system.net>
<authenticationModules>
<add type="Rei.Net.WSSEClient, WSSEClient" />
</authenticationModules>
</system.net>
</configuration>
History
- 25th June, 2007: Initial post