Skip to main content

JwtValidator

Struct JwtValidator 

Source
pub struct JwtValidator<R, V = DefaultVerifier> { /* private fields */ }
Expand description

Validates a compact JWT that is neither a credential nor a presentation: an OAuth 2.0 access token (RFC 9068), an ID token, a request object.

The checks follow RFC 7519 §7.2 and the best current practices of RFC 8725, in this order:

  1. The token is a compact JWS (CompactJws::decode), whose alg is one the options allow (RFC 8725 §3.1) and whose header typ is the one the options expect, if they expect one (RFC 8725 §3.11). typ is compared as RFC 7515 §4.1.9 says: ignoring case and an application/ prefix.
  2. The payload is a JSON claims set.
  3. The key the header names (KeyReference::from_jws, with the iss of the claims) is resolved for the KeyRole of the options and verifies the signature.
  4. Under IssuerBinding::KeyOfIssuer, the default, the key belongs to iss (KeyReference::binds_to): iss is the DID of a kid DID URL, the HTTPS issuer of a key set, or the did:jwk or thumbprint of a key by value.
  5. iss is the expected issuer, and aud includes the expected audience, when the options name them.
  6. exp, nbf and iat hold at the validation time, within the leeway.
  7. Every claim the options require is present.

What else a token must say (a scope, a client_id, a nonce) is the application’s to check, on the claims it gets back. Explicit typing is what keeps one kind of JWT from being accepted as another (RFC 8725 §3.11); name the typ whenever the token has one.

§Examples

An authorization server signs an access token with the Ed25519 key of RFC 8037 Appendix A.1, naming it by a DID URL while its iss is its HTTPS URL. The resource server pins that one key: its resolver knows no other, so the key need not belong to iss. A resolver that returns any key a token carries, such as JwkResolver, must never be combined with IssuerBinding::Any: anyone could sign with their own key.

use identity_kit::jose::{DefaultVerifier, Jwk, JwsAlgorithm, JwsHeader, JwtClaims, SignError, Signer, encode_compact};
use identity_kit::resolver::{
    IssuerBinding, JwtValidationError, JwtValidationOptions, JwtValidator, KeyReference,
    KeyResolutionError, KeyResolver, KeyRole,
};

struct Server(ed25519_dalek::SigningKey);

#[async_trait::async_trait]
impl Signer for Server {
    fn alg(&self) -> JwsAlgorithm { JwsAlgorithm::EdDSA }
    fn kid(&self) -> Option<&str> { None }
    async fn sign(&self, input: &[u8]) -> Result<Vec<u8>, SignError> {
        use ed25519_dalek::Signer as _;
        Ok(self.0.sign(input).to_bytes().to_vec())
    }
}

/// The authorization server's one key, under the one DID URL it signs with.
struct Pinned(Jwk);

#[async_trait::async_trait]
impl KeyResolver for Pinned {
    async fn resolve_key(&self, key: &KeyReference, _: KeyRole) -> Result<Jwk, KeyResolutionError> {
        match key {
            KeyReference::DidUrl(url) if url.as_str() == "did:example:as#key-1" => Ok(self.0.clone()),
            other => Err(KeyResolutionError::unsupported(other)),
        }
    }
}

let secret = [
    0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4,
    0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60,
];
let server = Server(ed25519_dalek::SigningKey::from_bytes(&secret));
let key = Jwk::from_ed25519_public(server.0.verifying_key().as_bytes())?;

let header = JwsHeader::new(JwsAlgorithm::EdDSA)
    .with_typ("at+jwt")
    .with_kid("did:example:as#key-1");
let mut claims = JwtClaims::new()
    .with_iss("https://as.example.com")
    .with_sub("5ba552d67")
    .with_aud("https://rs.example.com")
    .with_jti("dbe39bf3a3ba4238a513f51d6e1691c4")
    .with_claim("client_id", "s6BhdRkqt3")
    .with_claim("scope", "openid profile");
claims.iat = Some(1_700_000_000);
claims.exp = Some(1_700_003_600);
let token = encode_compact(&header, &serde_json::to_vec(&claims)?, &server).await?;

// The key is the pinned one, not the DID of `iss`, so the binding is waived.
let validator = JwtValidator::new(Pinned(key.clone()), DefaultVerifier);
let options = JwtValidationOptions::new()
    .with_typ("at+jwt")
    .with_algorithm(JwsAlgorithm::EdDSA)
    .with_issuer("https://as.example.com")
    .with_audience("https://rs.example.com")
    .with_issuer_binding(IssuerBinding::Any)
    .with_required_claims(["exp", "iat", "jti", "sub", "client_id"])
    .at(1_700_000_100);
let validated = validator.validate(&token, &options).await?;
assert_eq!(validated.claims.sub.as_deref(), Some("5ba552d67"));
assert_eq!(validated.key, key);

// A token naming any other key is refused.
let forged = encode_compact(
    &JwsHeader::new(JwsAlgorithm::EdDSA).with_typ("at+jwt").with_kid("did:example:attacker#key-1"),
    &serde_json::to_vec(&claims)?,
    &server,
)
.await?;
let error = validator.validate(&forged, &options).await.unwrap_err();
assert!(matches!(error, JwtValidationError::KeyResolution { .. }));

// An hour and a minute later it has expired.
let error = validator.validate(&token, &options.at(1_700_003_660)).await.unwrap_err();
assert!(matches!(error, JwtValidationError::Expired { expired_at: 1_700_003_600, .. }));

Implementations§

Source§

impl<R: KeyResolver, V: JwsVerifier> JwtValidator<R, V>

Source

pub fn new(resolver: R, verifier: V) -> Self

A validator resolving keys with resolver and verifying signatures with verifier.

Source

pub fn resolver(&self) -> &R

The key resolver.

Source

pub fn verifier(&self) -> &V

The signature verifier.

Source

pub async fn validate( &self, token: &str, options: &JwtValidationOptions, ) -> Result<ValidatedJwt, JwtValidationError>

Validates token, returning its claims as a JwtClaims set.

§Errors

The first JwtValidationError found, in the order JwtValidator lists the checks.

Source

pub async fn validate_as<T: DeserializeOwned>( &self, token: &str, options: &JwtValidationOptions, ) -> Result<ValidatedJwt<T>, JwtValidationError>

Validates token, returning its claims read into T, once the registered claims have been checked.

§Errors

As validate, and JwtValidationError::Claims when the payload does not fit T.

Trait Implementations§

Source§

impl<R: Debug, V: Debug> Debug for JwtValidator<R, V>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<R, V> Freeze for JwtValidator<R, V>
where R: Freeze, V: Freeze,

§

impl<R, V> RefUnwindSafe for JwtValidator<R, V>

§

impl<R, V> Send for JwtValidator<R, V>
where R: Send, V: Send,

§

impl<R, V> Sync for JwtValidator<R, V>
where R: Sync, V: Sync,

§

impl<R, V> Unpin for JwtValidator<R, V>
where R: Unpin, V: Unpin,

§

impl<R, V> UnsafeUnpin for JwtValidator<R, V>
where R: UnsafeUnpin, V: UnsafeUnpin,

§

impl<R, V> UnwindSafe for JwtValidator<R, V>
where R: UnwindSafe, V: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more