Open ycrumeyrolle opened 3 years ago
Cool! I'm not crazy! So let me explain what I'm trying to do: a maximal encapsulation of all this JWT stuff to be used by "Application developpers". I'm looking for the simplest API that can be imagined.
My approach so far is the following one:
A single service called IApplicationIdentity that reference a ILocalIdentity and a set of IRemoteParty. ILocalIdentity and IRemoteParty are interface ITrustedParty { string PartyName {get;} }
:
/// <summary>
/// Primary interface that encapsulates an application identity and its trusted parties.
/// This identity is theoretically independent of a process or an application domain but
/// in practice, an identity is tied to an <see cref="AppDomain"/>.
/// </summary>
public interface IApplicationIdentity
{
/// <summary>
/// Gets this domain application's identity.
/// </summary>
ILocalIdentity Local { get; }
/// <summary>
/// Gets the available trusted parties.
/// </summary>
IReadOnlyDictionary<JsonEncodedText, IRemoteParty> TrustedParties { get; }
/// <summary>
/// Writes a signed Json Web token binary representation of claims for a target <paramref name="audience"/> (that can be
/// this <see cref="Local"/> itself).
/// </summary>
/// <param name="claims">The payload.</param>
/// <param name="audience">The target audience.</param>
/// <param name="output">The binary output.</param>
/// <param name="options">Options that defaults to <see cref="JwtWriteOptions.Default"/>.</param>
void Sign( IEnumerable<KeyValuePair<string, object>> claims, ITrustedParty audience, IBufferWriter<byte> output, JwtWriteOptions? options = null );
/// <summary>
/// Writes an encrypted and signed token binary representation of claims for a target <paramref name="audience"/> (that can be
/// this <see cref="Local"/> itself).
/// </summary>
/// <param name="claims">The payload.</param>
/// <param name="audience">The target audience.</param>
/// <param name="output">The binary output.</param>
/// <param name="options">Options that defaults to <see cref="JwtWriteOptions.Default"/>.</param>
void Encrypt( IEnumerable<KeyValuePair<string, object>> claims, ITrustedParty audience, IBufferWriter<byte> output, JwtWriteOptions? options = null );
/// <summary>
/// Attempts to validate a Json Web Token.
/// </summary>
/// <param name="monitor">The monitor to use.</param>
/// <param name="utf8Token">The token.</param>
/// <returns>The <see cref="ValidatedJwt"/> for which <see cref="ValidatedJwt.Success"/> can be false.</returns>
ValidatedJwt Validate( IActivityMonitor monitor, ReadOnlySpan<byte> utf8Token );
}
The ValidatedJwt
is... minimalist:
/// <summary>
/// Minimalist definition of a token validation result.
/// </summary>
/// <remarks>
/// There is no status or other error description: validation failures often require a lot of details
/// and should be extensible (to handle specific rules and validators): we simply use the <see cref="CK.Core.ActivityMonitor"/>
/// to log any validation details (warnings or errors).
/// </remarks>
public readonly struct ValidatedJwt
{
readonly ReadOnlyMemory<byte>? _payload;
internal ValidatedJwt( ReadOnlyMemory<byte>? payload, bool enc )
{
_payload = payload;
IsEncrypted = enc;
}
/// <summary>
/// Gets whether the token validation succeeded.
/// </summary>
public bool Success => _payload is not null;
/// <summary>
/// Gets whether the token has been validated and was encrypted.
/// </summary>
public bool IsEncrypted { get; }
/// <summary>
/// Gets the raw Utf8 encoded payload.
/// This is null when <see cref="Success"/> is false.
/// </summary>
/// <returns>The payload text or null if <see cref="Success"/> is false.</returns>
public ReadOnlyMemory<byte>? Payload => _payload;
}
Key renewal, exchanges (and bindings to explicit certificates like PEM, PKCS#12, etc.) take place under the hood (via other abstractions), but the whole idea is here: this easy to use ApplicationIdentity somehow implements/encapsulates your https://github.com/uruk-project/Jwt/blob/master/docs/Security_considerations.md.
It's hard to make simple things...
Originally posted by @olivier-spinelli in https://github.com/uruk-project/Jwt/issues/545#issuecomment-779937175
This API is new in v2.0, still in beta, so there may be some remaining polish on the public API. The excepted usage is:
By doing so, there is a link between the issuer, the key, and the signature algorithm. The issuer is used as a lookup for finding the required validation. This is the common usage of a JWS: You have an issuer, a key, and an associated signature algorithm. So this is not a default algorithm.
It is also possible to do so (legacy usage):
The default issuer is not used as lookup, but only as the last chance to validate an issuer, as-well as the signature key. This allow some use cases where there is no issuer.
Your question convinces me on 2 points:
defaultAlgorithm
is a perfect example of name without clear intention.Require***ByDefault
,RequireAlgorithm
. Marking this methods as obsolete might be an option.