-
Notifications
You must be signed in to change notification settings - Fork 8
Server side storage
This page describes scenarios for server-side storage.
When validating signatures, the server will calculate the expected signature, and compare it to the one that was sent by the client. To do this, the server needs to be aware of the signing settings and configuration of that client. We need a store, which contains a list of known clients, with their signing configuration: A ClientStore.
When the feature is enabled, a list with used Nonce values for each Client is stored server-side. This is used to prevent replay attacks: The server will keep a record of the received nonce values. When a request is received and verified, but the nonce value has already been processed by the server in the past, signature verification will fail. We need a store, which contains used nonce values: A NonceStore.
These stores basically provide state data to the verification server.
The InMemory stores are included in the base Dalion.HttpMessageSigning.Verification package, and represent the default behavior.
But several other options are available. And you could roll-your-own.
Obviously, in load-balanced environments, an in-memory cache has to be shared between nodes. This is where the other options come into play. You can use the alternatives as a Distributed Cache.
These are the stores that we currently support:
Add a reference to the Verification NuGet package in your project:
dotnet add package Dalion.HttpMessageSigning.Verificationor
PM> Install-Package Dalion.HttpMessageSigning.VerificationImplement the IClientStore interfaces:
/// <summary>
/// Represents a store that the server can query to obtain client-specific settings for request signature verification.
/// </summary>
public interface IClientStore : IDisposable {
/// <summary>
/// Registers a <see cref="Client"/>, and its settings to perform <see cref="Signature"/> verification.
/// </summary>
/// <param name="client">The entry that represents a known client.</param>
Task Register(Client client);
/// <summary>
/// Gets the registered <see cref="Client"/> that corresponds to the specified identifier.
/// </summary>
/// <param name="clientId">The identifier of the registered client to get.</param>
/// <returns>The registered client that corresponds to the specified identifier.</returns>
Task<Client> Get(KeyId clientId);
}We recommend that you write some optional syntactical sugar.
Write an extension method to register it as the IClientStore in your IoC container, e.g.
public static IHttpMessageSigningVerificationBuilder UseRedisClientStore(this IHttpMessageSigningVerificationBuilder builder) {
return builder.UseClientStore(new RedisClientStore());
}Note
Notice that there is no
Deletemethod declared on the interface. We are of the opinion that this should not be a responsibility of this package, but rather of the containing application. There will always be business rules determining whether or not aClientcan be deleted, that are out of the scope of this package. E.g. You would need to implement logic that would allow aClientto only delete itself, not others. Or only allow deletion ofClients that have a certain claim.Declaring this method on the interface, would force all implementers to add this behavior.
Let us know what you think.
Add a reference to the Verification NuGet package in your project:
dotnet add package Dalion.HttpMessageSigning.Verificationor
PM> Install-Package Dalion.HttpMessageSigning.VerificationImplement the INonceStore interface:
/// <summary>
/// Represents a store that the server can use to check nonce validity, avoiding replay attacks.
/// </summary>
public interface INonceStore : IDisposable {
/// <summary>
/// Registers usage of the specified <see cref="Nonce"/> value.
/// </summary>
/// <param name="nonce">The <see cref="Nonce"/> that is received from a client.</param>
Task Register(Nonce nonce);
/// <summary>
/// Gets the <see cref="Nonce"/> value with a matching string, that was previously sent by the <see cref="Client"/>, identified by the specified <see cref="KeyId"/>.
/// </summary>
/// <param name="clientId">The identifier of the client to get the value for.</param>
/// <param name="nonceValue">The nonce string value that was sent.</param>
/// <returns></returns>
Task<Nonce> Get(KeyId clientId, string nonceValue);
}We recommend that you write some optional syntactical sugar.
Write an extension method to register it as an INonceStore in your IoC container, e.g.
public static IHttpMessageSigningVerificationBuilder UseRedisNonceStore(this IHttpMessageSigningVerificationBuilder builder) {
builder.Services.AddSingleton<INonceStore, RedisNonceStore>();
return builder;
}Note
We recommend that you include some kind of mechanism to remove expired nonces. Otherwise, your store will just keep on growing in size. And they are one-time throw-away values, anyway. Some examples:
HttpMessageSigning | David Lievrouw