2. Authentication Modules¶
What’s An Authentication Module?¶
Authentication modules verify the identity of clients that connect to AMPS. Although secure verification systems can be complex in practice, verification is simple in principle. Verification relies on a secret piece of information, such as a password that is only known to one person. If the client successfully provides that information, the client has proved its identity. More complex systems may require that the server prove its identity to a client before the client provides the secret, and takes steps to help keep the secret safe during the authentication process.
The authentication module interface provides AMPS with a way of verifying the identity of clients that connect to AMPS.
When To Implement an Authentication Module¶
When your AMPS installation needs to verify the identity of users who connect to AMPS, you implement an authentication module. The AMPS provided default authentication modules provide default functionality that enables AMPS to run without requiring explicit authentication.
Authentication Context¶
AMPS uses authentication modules to identify clients when the client connects and logs on. Therefore, use of an authentication module takes place within a specific transport. The same module may have different configuration settings for different transport settings. For example, one port may be enabled to publish offers to AMPS, and may require one type of authentication, while a different port, and different type of authentication, may be used for subscribers to the offers.
Because the same module may be used with different parameters on different transports, AMPS allows you to save the parameters in an authentication context. AMPS creates an authentication context for each thread that listens for connections on a given transport.
Your module implements the amps_authentication_create_context
function
to create an authentication context. The function has this signature:
amps_authentication_context amps_authentication_create_context(amps_module_options options);
The authentication context is a pointer to data that your module defines. The AMPS server does not process or interpret this data. The server receives the pointer from your module when the module is initialized, and then provides the pointer back to your module for each authentication request.
If your module is unable to successfully create the context, you return
a NULL context and use the amps_logger
provided to your module to
log information about the problem.
Notice that you cast the pointer to amps_authentication_context
before you return it, and cast the pointer back to the type that your
application uses after AMPS returns the pointer to you.
AMPS passes configuration options to modules as a pointer to an array of
amps_option
structs. AMPS indicates the end of the array with an
option that has a NULL
key. Your module processes options until it
reaches an option with a NULL key. If no options are specified in the
configuration file, the first amps_option
in the array will have a
NULL key.
The following snippet shows one way to process options:
for (; options->key != NULL; ++options) {
// process the key/value pair here
}
When AMPS exits, AMPS calls amps_authentication_destroy_context
with each
of the authentication contexts created. This gives your module an opportunity
to do any resource management, cleanup or logging necessary before AMPS shuts
down. The function has this signature:
int amps_authentication_destroy_context(amps_authentication_context context);
Authenticating Users with AMPS¶
AMPS passes authentication requests to your module by calling amps_authenticate()
. Your module
implements an amps_authenticate
function with the following signature:
int amps_authenticate(amps_authentication_context context,
const char* user,
size_t userLength,
const char* passwd,
size_t passwdLength,
char** userOut,
size_t* userOutLength,
char** passwdOut,
size_t* passwdOutLength);
The authentication context provided is the pointer returned by the call
to amps_authentication_create_context()
for the transport that
received the request. The user and password passed in to the function
are the credentials to authorize. Your function returns AMPS_SUCCESS
if authentication succeeds, AMPS_FAILURE
if authentication fails.
Your module can choose whether to provide the authenticated user name to
clients that receive published messages. If your module sets the
userOut
parameter, AMPS uses the value in that parameter as the
publisher for the connection. If you do not set this value, AMPS does
not provide the user name with messages published.
Following is a simple, minimal implementation of amps_authenticate:
int amps_authenticate(amps_authentication_context context,
const char* user,
size_t userLength,
const char* passwd,
size_t passwdLength,
char** userOut,
size_t* userOutLength,
char** passwdOut,
size_t* passwdOutLength)
{
// is_authorized (not shown) is a function that returns true if
// the password provided is correct for the user provided
if (is_authorized(user, userLength, passwd, passwdLength, context)) {
// Copy the user provided to userOut. The module
// can also choose not to provide a user name to AMPS by
// skipping this step.
// allocate space for the user name using the
// amps_allocator saved during module initialization
*userOut = (char*) amps_allocator(userLength+1);
memset(*userOut+userLength, 0, 1);
// Copy the user provided, set the length, and return.
strncpy(*userOut, user, userLength);
*userOutLength = userLength;
return AMPS_SUCCESS;
}
else {
return AMPS_FAILURE;
}
}
AMPS also supports challenge/response authentication with the
userOut
and passwdOut
parameters and the AMPS_RETRY
status.
To request that the client respond to a challenge, modules set the
response in the userOut
parameter, the challenge to be returned to
the client in the passwdOut
parameter and return an AMPS_RETRY
status. AMPS returns this information to the client, which can then
respond to the challenge in a subsequent requests.
Retrieving Additional Information¶
For some sites, a module may want or need additional information about the client logon request to be able to respond appropriately to the authentication request. For example, the module may want to reject requests from applications that don’t meet the client naming guidelines or requests that do not originate from an expected IP address.
Function | Retrieves |
amps_get_client_name |
The name of the current client as submitted in the logon request |
amps_get_connection_name |
The connection name for the current client, as assigned by AMPS |
amps_get_client_correlation_id |
The correlation ID for the current logon request, as submitted by the client in the logon request |
amps_get_message_type |
The message type for the logon request. |
amps_get_remote_address |
The address from which this connection was made. |
These methods return a pointer to a NULL-terminated string. This string must not be modified by the module. The string is not guaranteed to be available after the module returns, so if the module wants to save the information returned by the string, the module must make a copy of the returned information.
Retrieving Headers from Websocket Connections¶
For connections that arrive over the websocket interface, AMPS makes any headers included in the handshake request available through the following function:
int amps_symbol_scope_get(const char* key, size_t keyLength,
const char** value, size_t* valueLength);
The amps_symbol_scope_get
function retrieves the value of the
header by setting the value
to the the first character of
the value, and setting the valueLength
to the length of
the value. Notice that this does not make a copy of the
value, and the contents of the buffer are not guaranteed to
be valid after the amps_authenticate
function returns.
Important
The buffer that this function returns is managed by AMPS, and should not be freed by the module.
The buffer that this function returns is not guaranteed to be NULL-terminated.
For example, to retrieve the value of the X-Custom-Info
header
on a websocket authentication request, you might use code like the
following:
const char* value = 0;
size_t valueLength = 0;
int rc = amps_symbol_scope_get("X-Custom-Info", strlen("X-Custom-Info"),
&value, &valueLength);
if (rc == AMPS_SUCCESS && value != 0 && valueLength != 0)
{
/* use value & valueLength here */
}
else
{
/* header is not available, fallback or fail request */
}
Tip
When building a module that uses this mechanism, keep in mind that HTTP headers are only provided for websocket connections: you may need to use a different mechanism (such as the logon correlation ID) for sending the equivalent custom information for connections that use a different protocol.
Managing Authentication Contexts¶
For authentication modules, AMPS creates a separate context for each processing thread for a Transport rather than a single context for each place the module is used in the configuration file. The number of processing threads created depends on the capacity of the system, and may change from release to release of AMPS. An authentication module must assume that an arbitrary number of contexts are created for each Transport.
This has the following implications:
- State stored in an individual context is not shared across the Transport as a whole. For example, if your module needs to detect and prevent replay attacks, that state should be maintained in a global object (with appropriate locking) rather than being maintained in an individual context.
- When a module uses external resources, such as a connection to an authentication server, carefully consider whether the resource is specific to the transport processing thread, specific to the transport, or specific to the instance. Manage the resource appropriately. For example, you might choose to have a single connection to an authentication server for the entire instance, or a connection per transport.
Caution
An authentication module must assume that an arbitrary number of contexts are created for each Transport.
One common approach to maintaining state is to provide a global data
structure that stores shared state. For transport-specific state, the
structure can provide lookup by transport name, with the name of the
transport set on each context during context initialization, using the
amps_get_transport_name
utility function to find the transport name
(see Utility Functions for the utility functions
available).
Context Reset¶
AMPS provides the ability to reset the authentication contexts that provide service for a transport. When this occurs, for each context used by the transport, AMPS creates a new context and then destroys the old context.