Skip to content

Commit dfecd0b

Browse files
wesbiggsWes Biggs
and
Wes Biggs
authored
Add applicationContext to request object (#258)
Adds details for the `applicationContext` addition to the SIWF request. Update the library and generator as well. --------- Co-authored-by: Wes Biggs <[email protected]>
1 parent 26e9e79 commit dfecd0b

File tree

12 files changed

+156
-9
lines changed

12 files changed

+156
-9
lines changed

docs/src/DataStructures/All.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ This is JSON stringified and then [`base64url`](https://datatracker.ietf.org/doc
4343
### SMS/Phone
4444

4545
{{#include VerifiedPhone.md 0}}
46+
47+
## Context Data
48+
49+
### Application Context Credential
50+
51+
{{#include ApplicationContext.md 0}}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Application Context
2+
3+
## Schema
4+
5+
An application context credential MUST be a W3C Verifiable Credential document containing information about the application requesting user sign-in.
6+
7+
The `type` array for the credential should begin with the string `"ApplicationContextCredential"`.
8+
9+
The `credentialSchema` field should contain an object with the following fields:
10+
11+
- `type`: MUST be `JsonSchema`
12+
- `id`: MUST be the URL of the published application context credential schema. The latest version is [`https://schemas.frequencyaccess.com/ApplicationContextCredential/bciqe2bsnuaqg7zy3gqjmwha2q5h2bybvr6log2jsb5kjn2hos6irrlq.json`](https://schemas.frequencyaccess.com/ApplicationContextCredential/bciqe2bsnuaqg7zy3gqjmwha2q5h2bybvr6log2jsb5kjn2hos6irrlq.json).
13+
14+
The `credentialSubject` field should contain an object with the following fields:
15+
16+
- `id`: Your provider identifier, expressed as a DID.
17+
- `application`: A JSON object containing the following keys:
18+
- `name`: A map of one or more language tags to human-readable string values. Language tags should follow [BCP-47/RFC-5646](https://www.rfc-editor.org/rfc/rfc5646.html) (as used in the HTTP `Content-Language` header). A content language key of `"*"` indicates a wildcard or default value, as in HTTP.
19+
- `logo`: A map of one or more language tags (as above) to objects containing a `url` property. The URL should resolve to an image in the PNG format. The recommended maximum image width for optimal client compatibility is 200 pixels.
20+
21+
### Example
22+
23+
This application context credential describes a hypothetical app called "My Social App".
24+
25+
```json
26+
{
27+
"@context": [
28+
"https://www.w3.org/ns/credentials/v2",
29+
"https://www.w3.org/ns/credentials/undefined-terms/v2"
30+
],
31+
"type": [
32+
"ApplicationContextCredential",
33+
"VerifiableCredential"
34+
],
35+
"issuer": "did:web:frequencyaccess.com",
36+
"validFrom": "2025-02-12T21:28:08.289+0000",
37+
"credentialSchema": {
38+
"type": "JsonSchema",
39+
"id": "https://schemas.frequencyaccess.com/ApplicationContextCredential/bciqe2bsnuaqg7zy3gqjmwha2q5h2bybvr6log2jsb5kjn2hos6irrlq.json"
40+
},
41+
"credentialSubject": {
42+
"id": "did:dsnp:123456",
43+
"application": {
44+
"name": {
45+
"en": "My Social App",
46+
"es": "Mi Aplicación Social"
47+
},
48+
"logo": {
49+
"*": {
50+
"url": "https://example.org/logos/my-social-app.png"
51+
}
52+
}
53+
}
54+
},
55+
"proof": {
56+
"type": "DataIntegrityProof",
57+
"verificationMethod": "did:web:frequencyaccess.com#z6MkofWExWkUvTZeXb9TmLta5mBT6Qtj58es5Fqg1L5BCWQD",
58+
"cryptosuite": "eddsa-rdfc-2022",
59+
"proofPurpose": "assertionMethod",
60+
"proofValue": "z4jArnPwuwYxLnbBirLanpkcyBpmQwmyn5f3PdTYnxhpy48qpgvHHav6warjizjvtLMg6j3FK3BqbR2nuyT2UTSWC"
61+
}
62+
}
63+
```

docs/src/DataStructures/SignedRequest.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@
4646
}
4747
]
4848
}
49-
]
49+
],
50+
"applicationContext": {
51+
"url": "https://example.org/myapp/siwf-manifest.json",
52+
"hash": [
53+
"bciqmdvmxd54zve5kifycgsdtoahs5ecf4hal2ts3eexkgocyc5oca2y"
54+
]
55+
}
5056
}
5157
```

docs/src/SignatureGeneration.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,26 @@ Supported Options:
100100

101101
- See a full list of [Available Credentials](./Credentials.md)
102102

103+
## Step 4 (Recommended): Provide Application Context
104+
105+
To help users understand which application is asking them to sign in, you can provide an `applicationContext` object that contains the URL of an application context credential.
106+
This is especially important if you want to manage multiple applications under a single provider identity.
107+
Note that any delegations that are granted by the user will apply to all applications for that provider.
108+
109+
```json
110+
{
111+
// ...
112+
"applicationContext": {
113+
"url": "https://example.org/myapp/siwf-manifest.json"
114+
}
115+
}
116+
```
117+
118+
The application context credential is a JSON-formatted verifiable credential that MAY be signed by a credential issuer.
119+
If you are integrating with Frequency Access, your application context details will be created as part of the provisioning process.
120+
121+
For detailed information on the credential schema and properties, see the [full specification and example](./DataStructures/ApplicationContext.md).
122+
103123
### Example Using TypeScript/JavaScript
104124

105125
```typescript
@@ -120,6 +140,11 @@ const credentials = [
120140
siwf.VerifiedGraphKeyCredential,
121141
];
122142

143+
// This is a reference to an application context credential
144+
const applicationContext = {
145+
url: "https://example.org/myapp/context.json",
146+
};
147+
123148
// This is the URI that the user should return to after authenticating
124149
const callbackUri: string = getWebOrApplicationCallbackUri();
125150

@@ -130,6 +155,7 @@ const encodedSignedRequest = await siwf.generateEncodedSignedRequest(
130155
callbackUri,
131156
permissions,
132157
credentials,
158+
applicationContext,
133159
);
134160

135161
// Options with endpoint selection

libraries/js/src/request.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,19 @@ const stockCredentials = [
1717
VerifiedGraphKeyCredential,
1818
];
1919

20+
const exampleApplicationContext = {
21+
url: 'https://example.org/myapp/siwf-manifest.json',
22+
};
23+
2024
describe('request', () => {
2125
it('correctly generates the signed request', async () => {
22-
const generated = await generateSignedRequest('//Alice', 'http://localhost:3000', [1, 2, 100], stockCredentials);
26+
const generated = await generateSignedRequest(
27+
'//Alice',
28+
'http://localhost:3000',
29+
[1, 2, 100],
30+
stockCredentials,
31+
exampleApplicationContext
32+
);
2333

2434
expect(generated).toEqual({
2535
requestedSignatures: {
@@ -57,6 +67,7 @@ describe('request', () => {
5767
hash: ['bciqmdvmxd54zve5kifycgsdtoahs5ecf4hal2ts3eexkgocyc5oca2y'],
5868
},
5969
],
70+
applicationContext: exampleApplicationContext,
6071
});
6172
});
6273

libraries/js/src/request.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,22 @@ export async function generateSignedRequest(
6666
providerKeyUri: string,
6767
callbackUri: string,
6868
permissions: number[],
69-
credentials: SiwfCredentialRequest[] = []
69+
credentials: SiwfCredentialRequest[] = [],
70+
applicationContext: { url: string } | null = null
7071
): Promise<SiwfSignedRequest> {
7172
await cryptoWaitReady();
7273
const keyPair = keyring.createFromUri(providerKeyUri);
7374

7475
const signature = keyPair.sign(generateRequestSigningData(callbackUri, permissions, true), {});
7576

76-
return buildSignedRequest(u8aToHex(signature), keyPair.address, callbackUri, permissions, credentials);
77+
return buildSignedRequest(
78+
u8aToHex(signature),
79+
keyPair.address,
80+
callbackUri,
81+
permissions,
82+
credentials,
83+
applicationContext
84+
);
7785
}
7886

7987
/**
@@ -92,7 +100,8 @@ export function buildSignedRequest(
92100
signerPublicKey: string,
93101
callbackUri: string,
94102
permissions: number[],
95-
credentials: SiwfCredentialRequest[] = []
103+
credentials: SiwfCredentialRequest[] = [],
104+
applicationContext: { url: string } | null = null
96105
): SiwfSignedRequest {
97106
if (!isSiwfCredentialsRequest(credentials)) {
98107
console.error('credentials', credentials);
@@ -120,6 +129,7 @@ export function buildSignedRequest(
120129
},
121130
},
122131
requestedCredentials,
132+
applicationContext: applicationContext || undefined,
123133
};
124134
}
125135

@@ -137,9 +147,16 @@ export async function generateEncodedSignedRequest(
137147
providerKeyUri: string,
138148
callbackUri: string,
139149
permissions: number[],
140-
credentials: SiwfCredentialRequest[] = []
150+
credentials: SiwfCredentialRequest[] = [],
151+
applicationContext: { url: string } | null = null
141152
): Promise<string> {
142-
const signedRequest = await generateSignedRequest(providerKeyUri, callbackUri, permissions, credentials);
153+
const signedRequest = await generateSignedRequest(
154+
providerKeyUri,
155+
callbackUri,
156+
permissions,
157+
credentials,
158+
applicationContext
159+
);
143160
return encodeSignedRequest(signedRequest);
144161
}
145162

libraries/js/src/types/request.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface SiwfSignedRequest {
2727
};
2828
};
2929
requestedCredentials?: SiwfCredentialRequest[];
30+
applicationContext?: { url: string };
3031
}
3132

3233
function isSiwfCredential(input: unknown): input is SiwfCredential {

tools/signed-request-generator/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/signed-request-generator/src/components/Form.svelte

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
let isManualSign = false;
2121
let permissions: number[] = [];
2222
let credentials: SiwfCredential[] = [];
23+
let applicationContextPlaceholder = 'https://example.org/my-app-context.json';
24+
let applicationContext = '';
2325
let isRequiredComplete = false;
2426
2527
let manualSignature = '';
@@ -47,7 +49,8 @@
4749
signerPublicKey,
4850
callbackUri,
4951
permissions,
50-
credentials
52+
credentials,
53+
{ url: applicationContext }
5154
);
5255
encodedRequest = encodeSignedRequest(signedRequest);
5356
requestJson = JSON.stringify(signedRequest, null, 2);
@@ -76,6 +79,16 @@
7679
<div class="mb-4">
7780
<Credentials bind:credentials />
7881
</div>
82+
<div class="mb-4">
83+
<label for="applicationContext">Application Context URL*</label>
84+
<input
85+
type="url"
86+
id="applicationContext"
87+
bind:value={applicationContext}
88+
required
89+
placeholder={applicationContextPlaceholder}
90+
/>
91+
</div>
7992
{#if isRequiredComplete}
8093
<div class="mb-4">
8194
<fieldset style="min-width: 0;">

tools/signed-request-generator/tests/basic.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ test('can see wallet button when I add a url', async ({ page }) => {
44
await page.goto('/');
55
await expect(page.locator('#callbackUri')).toBeVisible();
66
await page.locator('#callbackUri').fill('http://localhost:3000');
7+
await page.locator('#applicationContext').fill('http://example.org');
78
await expect(page.locator('button', { hasText: 'Connect to Wallet' })).toBeVisible();
89
});

tools/signed-request-generator/tests/see-signature.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test('can see an output when I add a url and signature data', async ({ page })
44
await page.goto('/');
55
await expect(page.locator('#callbackUri')).toBeVisible();
66
await page.locator('#callbackUri').fill('http://localhost:3000');
7+
await page.locator('#applicationContext').fill('http://example.org');
78
await page.locator('#signMethod-manual').check();
89
await expect(page.locator('#signerPublicKey')).toBeVisible();
910

tools/signed-request-generator/tests/signature-reset.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ test('can see the signature removed when edit signature permission data', async
44
await page.goto('/');
55
await expect(page.locator('#callbackUri')).toBeVisible();
66
await page.locator('#callbackUri').fill('http://localhost:3000');
7+
await page.locator('#applicationContext').fill('http://example.org');
78
await page.locator('#signMethod-manual').check();
89
await expect(page.locator('#signerPublicKey')).toBeVisible();
910

@@ -18,6 +19,7 @@ test('can see the signature removed when edit signature callback data', async ({
1819
await page.goto('/');
1920
await expect(page.locator('#callbackUri')).toBeVisible();
2021
await page.locator('#callbackUri').fill('http://localhost:3000');
22+
await page.locator('#applicationContext').fill('http://example.org');
2123
await page.locator('#signMethod-manual').check();
2224
await expect(page.locator('#signerPublicKey')).toBeVisible();
2325

0 commit comments

Comments
 (0)