Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom JWT claims for presentations #1244

Merged
merged 3 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use identity_iota::credential::DecodedJwtPresentation;
use wasm_bindgen::prelude::*;

use crate::common::RecordStringAny;
use crate::common::WasmTimestamp;
use crate::credential::UnknownCredential;
use crate::credential::WasmPresentation;
Expand Down Expand Up @@ -55,6 +56,18 @@ impl WasmDecodedJwtPresentation {
pub fn audience(&self) -> Option<String> {
self.0.aud.clone().map(|aud| aud.to_string())
}

/// The custom claims parsed from the JWT.
#[wasm_bindgen(js_name = customClaims)]
pub fn custom_claims(&self) -> Option<RecordStringAny> {
match &self.0.custom_claims {
Some(claims) => JsValue::from_serde(&claims.clone())
.map(|js_val| js_val.unchecked_into::<RecordStringAny>())
.ok(),

None => None,
}
}
}

impl From<DecodedJwtPresentation<UnknownCredential>> for WasmDecodedJwtPresentation {
Expand Down
5 changes: 5 additions & 0 deletions bindings/wasm/src/storage/jwt_presentation_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ interface IJwtPresentationOptions {
* Default: `undefined`.
*/
readonly audience?: string;

/**
* Custom claims that be used to set additional claims on the resulting JWT.
abdulmth marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly customClaims?: Record<string, any>;
}"#;
4 changes: 4 additions & 0 deletions bindings/wasm/tests/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ describe("#JwkStorageDocument", function() {
expirationDate,
issuanceDate: Timestamp.nowUTC(),
audience,
customClaims: {
testKey: "testValue",
},
}),
);

Expand All @@ -377,6 +380,7 @@ describe("#JwkStorageDocument", function() {
presentation.toJSON(),
);
assert.equal(decoded.audience(), audience);
assert.deepStrictEqual(decoded.customClaims(), { testKey: "testValue" });

// check issuance date validation.
let options = new JwtPresentationValidationOptions({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_core::common::Object;
use serde::Deserialize;
use serde::Serialize;

Expand All @@ -20,6 +21,8 @@ pub struct JwtPresentationOptions {
/// Sets the audience for presentation (`aud` property in JWT claims).
/// Default: `None`.
pub audience: Option<Url>,
/// Custom claims that be used to set additional claims on the resulting JWT.
abdulmth marked this conversation as resolved.
Show resolved Hide resolved
pub custom_claims: Option<Object>,
}

impl JwtPresentationOptions {
Expand Down Expand Up @@ -48,6 +51,7 @@ impl Default for JwtPresentationOptions {
expiration_date: None,
issuance_date: Some(Timestamp::now_utc()),
audience: None,
custom_claims: None,
}
}
}
6 changes: 6 additions & 0 deletions identity_credential/src/presentation/jwt_serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ where
pub(crate) aud: Option<Url>,

pub(crate) vp: InnerPresentation<'presentation, CRED, T>,

#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub(crate) custom: Option<Object>,
}

impl<'presentation, CRED, T> PresentationJwtClaims<'presentation, CRED, T>
Expand Down Expand Up @@ -91,6 +94,7 @@ where
exp: options.expiration_date.map(|expiration_date| expiration_date.to_unix()),
issuance_date: options.issuance_date.map(IssuanceDateClaims::new),
aud: options.audience.clone(),
custom: options.custom_claims.clone(),
})
}
}
Expand Down Expand Up @@ -146,6 +150,7 @@ where
jti,
aud: _,
vp,
custom: _,
} = self;
let InnerPresentation {
context,
Expand Down Expand Up @@ -247,6 +252,7 @@ mod test {
expiration_date: Some(Timestamp::from_unix(1694699551).unwrap()),
issuance_date: Some(Timestamp::from_unix(1694698951).unwrap()),
audience: None,
custom_claims: None,
};
let claims: PresentationJwtClaims<'_, Jwt> =
PresentationJwtClaims::<'_, Jwt>::new(&presentation, &options).unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ pub struct DecodedJwtPresentation<CRED, T = Object> {
pub issuance_date: Option<Timestamp>,
/// The `aud` property parsed from the JWT claims.
pub aud: Option<Url>,
/// The custom claims parsed from the JWT.
pub custom_claims: Option<Object>,
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2020-2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use identity_core::common::Object;
use identity_core::common::Timestamp;
use identity_core::common::Url;
use identity_core::convert::FromJson;
Expand Down Expand Up @@ -145,6 +146,7 @@ where
))?;

let aud: Option<Url> = claims.aud.clone();
let custom_claims: Option<Object> = claims.custom.clone();

let presentation: Presentation<CRED, T> = claims.try_into_presentation().map_err(|err| {
CompoundJwtPresentationValidationError::one_presentation_error(JwtValidationError::PresentationStructure(err))
Expand All @@ -156,6 +158,7 @@ where
expiration_date,
issuance_date,
aud,
custom_claims,
};

Ok(decoded_jwt_presentation)
Expand Down
12 changes: 12 additions & 0 deletions identity_storage/src/storage/tests/presentation_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,17 @@ where
.build()
.unwrap();

let mut custom_claims = Object::new();
custom_claims.insert(
"test-key".to_owned(),
serde_json::Value::String("test-value".to_owned()),
);

let presentation_options = JwtPresentationOptions {
expiration_date: Some(Timestamp::now_utc().checked_add(Duration::hours(10)).unwrap()),
issuance_date: Some(Timestamp::now_utc().checked_sub(Duration::hours(10)).unwrap()),
audience: Some(Url::parse("did:test:123").unwrap()),
custom_claims: Some(custom_claims),
};

let presentation_jwt = setup
Expand Down Expand Up @@ -87,6 +94,7 @@ where
);
assert_eq!(decoded_presentation.issuance_date, presentation_options.issuance_date);
assert_eq!(decoded_presentation.aud, presentation_options.audience);
assert_eq!(decoded_presentation.custom_claims, presentation_options.custom_claims);
}

#[tokio::test]
Expand Down Expand Up @@ -116,6 +124,7 @@ where
expiration_date: Some(Timestamp::now_utc().checked_add(Duration::hours(10)).unwrap()),
issuance_date: Some(Timestamp::now_utc().checked_sub(Duration::hours(10)).unwrap()),
audience: Some(Url::parse("did:test:123").unwrap()),
custom_claims: None,
};

let presentation_jwt = setup
Expand Down Expand Up @@ -172,6 +181,7 @@ where
expiration_date: Some(Timestamp::now_utc().checked_add(Duration::hours(10)).unwrap()),
issuance_date: Some(Timestamp::now_utc().checked_sub(Duration::hours(10)).unwrap()),
audience: Some(Url::parse("did:test:123").unwrap()),
custom_claims: None,
};

let presentation_jwt = setup
Expand Down Expand Up @@ -254,6 +264,7 @@ where
issuance_date: None,
expiration_date: Some(Timestamp::now_utc().checked_sub(Duration::days(1)).unwrap()),
audience: None,
custom_claims: None,
};

let presentation_jwt = setup
Expand Down Expand Up @@ -318,6 +329,7 @@ where
issuance_date: Some(Timestamp::now_utc().checked_add(Duration::hours(1)).unwrap()),
expiration_date: None,
audience: None,
custom_claims: None,
};

let presentation_jwt = setup
Expand Down
Loading