Authentication with DAVServer's Embedded HTTP Server
Introduction
The DAVServer component can be hosted in one of three modes: using its built-in embedded HTTP server, within an external server framework such as ASP.NET Core, or in a fully offline mode. This article focuses on user authentication when the component is hosted via the embedded HTTP server, detailing how incoming requests are authenticated using supported mechanisms like Basic, Digest, NTLM, and Negotiate (Kerberos/NTLM).
Configuring the Server
To begin, the component should be configured to use its embedded HTTP server. This is done by setting the ProcessingMode property and calling StartListening to begin accepting incoming requests. Additionally, the AllowedAuthMethods field (of the ServerSettings property) can be set to specify which authentication methods the server will accept from clients. For example:
// Enable embedded server
server.ProcessingMode = DAVServerProcessingModes.modeEmbeddedServer;
<br/>
// Enable specific authentication methods
server.ServerSettings.AllowedAuthMethods = "Anonymous,Basic,Digest,NTLM,Negotiate";
<br/>
server.ServerSettings.LocalHost = "localhost";
server.ServerSettings.LocalPort = "80";
server.StartListening();
See the sections below for details on handling each authentication method.
Handling Events
During authentication, two key events may need to be handled depending on which authentication method is in use: AuthInfo and AuthRequest. These events expose information about the current authentication request and allow the application to inspect and optionally validate authentication details.
AuthInfo
The AuthInfo event is triggered when a request includes authentication credentials (except in the case of anonymous access). It exposes the following parameters:
- AuthMethod - The authentication method being used (e.g., "Basic", "Digest", "NTLM", or "Kerberos").
- User - The username extracted from the request.
- Realm - The authentication realm, if applicable.
- Password - A placeholder to provide the user's password (only applicable for Basic and Digest).
This event provides an opportunity to log or inspect the authentication attempt, and is especially useful for methods like Basic or Digest where the server-side should provide the user's stored password to ensure the request contains correct authentication information.
AuthRequest
Immediately after AuthInfo, the AuthRequest event fires. It exposes the same parameters as AuthInfo, with the addition of:
- KerberosSPN - The Service Principal Name used during Kerberos authentication (only relevant for Negotiate/Kerberos).
In this event, you can perform any final validation, such as verifying the SPN used in a Kerberos ticket. You may also explicitly accept or reject the request based on the provided details by setting the Accept parameter.
Together, these events provide complete visibility into the authentication process and are key to handling custom authentication logic. For detailed instructions on handling each specific authentication method, please refer to the Allowed Authentication Methods section below.
Allowed Authentication Methods
As mentioned, the AllowedAuthMethods field can be used to specify which authentication methods are permitted for incoming requests. Each method has its own requirements and behavior, which are detailed below.
By default, all authentication methods are enabled by default, including anonymous access. This means that requests without an Authorization header will be accepted. To require authentication, simply remove "Anonymous" from the comma-separated list assigned to AllowedAuthMethods.
Anonymous Authentication
When "Anonymous" is included in AllowedAuthMethods, incoming requests that do not contain an Authorization header will be automatically accepted. In this case, no additional handling is required to authenticate the request.
If other authentication methods are also specified alongside anonymous (e.g., "Anonymous,Basic"), and a request includes an Authorization header matching one of those methods, the component will attempt to authenticate the request using that method instead.
However, if a request includes an Authorization header for a method that is not listed in AllowedAuthMethods (e.g., "Digest" in the above case), then the header will be ignored, and the request will be treated as anonymous.
Basic Authentication
When "Basic" is included in AllowedAuthMethods, basic authentication will be performed on requests containing an Authorization header specifying "Basic". During basic authentication, the client sends a base64-encoded string containing a username and password. These credentials are processed as described below.
First, the AuthInfo event will fire, providing the User and Realm associated with the current request. To handle this event appropriately, the Password parameter should be set to the expected password for the given user and realm. For example:
server.OnAuthInfo += (o, e) => {
if (e.AuthMethod.Equals("Basic")) {
// LookupPassword is some arbitrary method to check some credential database for the user's password
e.Password = LookupPassword(e.User, e.Realm);
}
};
After AuthInfo returns, the component will compare the provided password (if specified) with the password sent by the client.
Authentication then proceeds to the AuthRequest event. If the specified password matches the password sent by the client, the Accept parameter will be set to true. Otherwise, if the passwords do not match, or no password was specified, Accept will be false.
In either case where Accept is false, the Password parameter of AuthRequest will contain the password sent by the client. The user may perform additional validation at this point and override Accept if needed. For example:
server.OnAuthRequest += (o, e) => {
if (e.Accept) return;
<br/>
if (e.AuthMethod.Equals("Basic")) {
// LookupPassword is some arbitrary method to check some credential database for the user's password
string requestPassword = e.Password;
if requestPassword.Equals(LookupPassword(e.User, e.Realm)) e.Accept = true;
}
};
Digest Authentication
When "Digest" is included in AllowedAuthMethods, digest authentication will be performed on requests containing an Authorization header specifying "Digest".
Digest authentication is a challenge-response mechanism designed to securely verify a user's credentials without transmitting the password in cleartext. When a client attempts to access a protected resource without valid credentials, the server responds with a 401 Unauthorized status and includes a WWW-Authenticate header containing a digest challenge. This challenge includes several parameters needed by the client to compute a valid response, such as the realm, nonce, algorithm, and others.
The client uses these values, along with its stored password and additional request data, to compute a hash (i.e., the "response") and sends this back to the server in the Authorization header.
Once this response is received, the AuthInfo event fires, providing the User and Realm associated with the response. To handle this event properly, set the Password parameter to the expected password for the given user and realm. For example:
server.OnAuthInfo += (o, e) => {
if (e.AuthMethod.Equals("Digest")) {
// LookupPassword is some arbitrary method to check some credential database for the users password
e.Password = LookupPassword(e.User, e.Realm);
}
};
After this event returns, the component uses the supplied password to internally calculate the expected digest response and compare it with the response from the client.
Authentication then proceeds to the AuthRequest event. If the hashes match, the Accept parameter will be set to true. Otherwise, if the hashes do not match or if no password was supplied, Accept will be false.
Unlike basic authentication, the actual password is never transmitted by the client, so it is not possible to extract or verify it directly. In cases where authentication fails, the Password field in the AuthRequest event will either be empty or the same as what was previously set in AuthInfo, indicating that digest authentication was unsuccessful.
While it's technically possible to manually override the Accept parameter, doing so is not recommend for digest authentication. For example:
server.OnAuthRequest += (o, e) => {
if (e.AuthMethod.Equals("Digest")) {
return; // Just return, not recommended to manually override 'Accept' here
}
};
NTLM Authentication
When "NTLM" is included in AllowedAuthMethods, NTLM authentication will be performed on requests containing an Authorization header specifying "NTLM".
NTLM is a Windows-based challenge-response authentication protocol designed to authenticate users without sending their password over the network. Unlike Digest or Basic authentication, NTLM uses a multi-step handshake involving cryptographic challenges and responses based on the user's credentials.
When a client initiates a request to a protected resource without credentials, the server responds with a 401 Unauthorized status and a WWW-Authenticate header indicating NTLM is supported. The client then begins a multi-step handshake, which is handled automatically by the component.
If the NTLM handshake completes successfully, the component will first fire the AuthInfo event to indicate the authenticated user's name. This event is informational only and is not used to provide credentials, since the user's identity has already been validated by the component (or system). For example:
server.OnAuthInfo += (o, e) => {
if (e.AuthMethod.Equals("NTLM")) {
Console.WriteLine("User authenticated using NTLM: " + e.User);
}
};
After this event returns, AuthRequest will fire. As with the previous event, this is only informational, as the user has been successfully authenticated. The Accept parameter will always be true in this case. If necessary, the Accept parameter may be overridden at this point.
Negotiate Authentication
When "Negotiate" is included in AllowedAuthMethods, Negotiate authentication will be performed on requests containing an Authorization header specifying "Negotiate".
Negotiate is an authentication mechanism that allows the client and server to negotiate the most secure available method. This typically results in either Kerberos ot NTLM being used, depending on what is supported and available on the client and server sides. Kerberos is preferred by many systems when available, while NTLM acts as a fallback if Kerberos cannot be used (e.g., when the client is not joined to a domain).
When Kerberos is used and the server is joined to a domain, you may configure the expected SPN via the KerberosSPN configuration setting. This ensures the SPN used by the client matches what the server is expecting. For example:
server.Config("KerberosSPN=HTTP/http-server.example.com");
To clarify, the component in this case is acting as a service principal, and KerberosSPN is used to specify the name of the service principal. Please note that there is some additional setup required to ensure Kerberos may be utilized here. Please see the section here for additional details on setting up a service principal name for the application/service (i.e., the application running the DAVServer component) here.
The authentication flow begins similarly to NTLM: the client makes an unauthenticated request, and the server replies with a 401 Unauthorized status along with a WWW-Authenticate: Negotiate header. From there, the client initiates a token exchange to authenticate using either Kerberos or NTLM. This exchange is handled by the underlying Windows SSPI or equivalent system service.
Assuming that the user has been successfully authenticated with the system, AuthInfo will fire first, providing the name of the authenticated user. In this case, the AuthMethod parameter may be either Kerberos or NTLM, depending on which mechanism was used by the underlying security API.
server.OnAuthInfo += (o, e) => {
if (e.AuthMethod.Equals("Kerberos")) {
Console.WriteLine("User authenticated using Kerberos: " + e.User);
}
if (e.AuthMethod.Equals("NTLM")) {
Console.WriteLine("User authentication using NTLM: " + e.User);
}
};
After this event returns, the AuthRequest event will fire. If the AuthMethod parameter is "NTLM", this event can be handled as described in the previous section. However, if the AuthMethod parameter is "Kerberos", the behavior differs.
If the KerberosSPN config was previously set, the component will validate the SPN used during authentication (i.e., the SPN specified in the Kerberos ticket) against the configured value. If they match, authentication proceeds successfully. If they do not match, the Accept parameter in AuthRequest will be false, and the authentication will be rejected unless overridden manually. The AuthRequest event also contains a KerberosSPN parameter, specifying the SPN associated with the request. So, rather than setting the mentioned config, the SPN specified in the Kerberos ticket may be checked here as well.
This is an important security measure, as the underlying system may accept Kerberos tickets meant for another SPN on the system. Please see below for an example of AuthRequest.
server.OnAuthRequest += (o, e) => {
// Assuming we set the KerberosSPN config, no action is required
if (e.Accept) return;
<br/>
// Manual SPN check if config wasn't used
if (e.KerberosSPN.Equals("HTTP/http-server.example.com")) {
e.Accept = true;
}
};
Server-Side Setup for Negotiate (Kerberos)
When "Negotiate" is specified via AllowedAuthMethods, and the server intends to support Kerberos, some additional configuration is required to ensure authentication works as expected.
The DAVServer component must be running on a Windows machine joined to a domain in order to utilize Kerberos with Negotiate. If this is not the case, then NTLM will be utilized as the fallback option for Negotiate instead.
Assuming the component is running on a Windows machine joined to a domain, the embedded HTTP server here will act as a Kerberos service principal. First, a new account should be created in the Microsoft Active Directory for the service.
To create a new account, navigate to the Server Manager and open the Active Directory Users and Computers menu item. In this example, we will create a new user account with the first name and login name http-server. Please set the password appropriately and ensure that the user does not need to change the password. Next, you will need to set the SPN (Service Principal Name) for the service. In this example, we are using the setspn command:
setspn -S http/http-server.example.com http-server
Note that in the above example, http-server represents the account name associated with the service. This may be set to the domain/name of the target computer, or a user account, according to the setspn command documentation. The '-C' option may be used to indicate that the account name is a computer account, and the '-U' option may be used to indicate that the account name is a user account. Typically (and in this example) this account is specified as the account that the application/service will run under.
Additionally, http-server.example.com is the name of the machine (under the common domain example.com). The SPN http/http-server.example.com can be adjusted as necessary, and is only following typical naming conventions.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@callback.com.