diff --git a/package.json b/package.json index 844448df2..155aa963f 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@web5/api": "0.8.4", "@web5/credentials": "0.4.1", "@web5/dids": "0.2.3", + "@web5/identity-agent": "0.2.5", "font-awesome": "4.7.0", "googleapis": "128.0.0", "node-fetch": "3.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b78c69bf..59771b875 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@web5/dids': specifier: 0.2.3 version: 0.2.3 + '@web5/identity-agent': + specifier: 0.2.5 + version: 0.2.5 font-awesome: specifier: 4.7.0 version: 4.7.0 @@ -4514,6 +4517,7 @@ packages: dependencies: is-glob: 4.0.3 micromatch: 4.0.5 + napi-wasm: 1.1.0 dev: true bundledDependencies: - napi-wasm @@ -6416,6 +6420,19 @@ packages: - supports-color dev: false + /@web5/identity-agent@0.2.5: + resolution: {integrity: sha512-TuTT8EYUICP0F64nYfQeXPKSR8dvxGeRDO40XreuSOYGKSxa2jtMxH5LAhakT42GWWAtvB1yfVdTWjfLq6Y6pQ==} + engines: {node: '>=18.0.0'} + dependencies: + '@web5/agent': 0.2.5 + '@web5/common': 0.2.2 + '@web5/crypto': 0.2.2 + '@web5/dids': 0.2.3 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@web5/user-agent@0.2.5: resolution: {integrity: sha512-qv5M698C5HSvq30xUgLWtcsbZppjfOH5qZthpTRx4ItL5UWA/eQ9DsQiQeb4vet3uIUy3NHRDIQezclOdwYErw==} engines: {node: '>=18.0.0'} @@ -13932,6 +13949,10 @@ packages: resolution: {integrity: sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==} dev: false + /napi-wasm@1.1.0: + resolution: {integrity: sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg==} + dev: true + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true diff --git a/site/__tests__/web5/build/decentralized-web-nodes/use-identity-agents.test.js b/site/__tests__/web5/build/decentralized-web-nodes/use-identity-agents.test.js new file mode 100644 index 000000000..ce994a859 --- /dev/null +++ b/site/__tests__/web5/build/decentralized-web-nodes/use-identity-agents.test.js @@ -0,0 +1,24 @@ +import { test, expect, describe } from 'vitest'; +import { + getDwnEndpoints, +} from '../../../../code-snippets/web5/build/decentralized-web-nodes/use-identity-agents'; + +let agent; + +describe('create identity agent', () => { + // TO DO: add more tests for each code snippets after the team determines how to conditionally run tests for Web5.connect() vs. the Identity Agent + test('createDidOptions returns an object with cryptographic keys and service endpoints', async () => { + const didOptions = await getDwnEndpoints(); + + expect(didOptions).toHaveProperty('keySet.verificationMethodKeys'); + expect(Array.isArray(didOptions.keySet.verificationMethodKeys)).toBe(true); + expect(didOptions).toHaveProperty('services'); + expect(Array.isArray(didOptions.services)).toBe(true); + didOptions.services.forEach(service => { + expect(service).toHaveProperty('id'); + expect(service).toHaveProperty('serviceEndpoint'); + expect(service).toHaveProperty('type'); + expect(service.type).toBe('DecentralizedWebNode'); + }); + }); +}); diff --git a/site/code-snippets/web5/build/decentralized-web-nodes/use-identity-agents.js b/site/code-snippets/web5/build/decentralized-web-nodes/use-identity-agents.js new file mode 100644 index 000000000..f5b60849c --- /dev/null +++ b/site/code-snippets/web5/build/decentralized-web-nodes/use-identity-agents.js @@ -0,0 +1,57 @@ +import { IdentityAgent } from '@web5/identity-agent'; +import { getTechPreviewDwnEndpoints } from '@web5/api'; +import { DidIonMethod } from '@web5/dids'; + + +export async function createIdentityAgent() { + const agent = await IdentityAgent.create(); + return agent; +} + +export async function authenticateIdentityAgent(agent) { + await agent.start({ passphrase: 'default-passphrase' }); + return agent.agentDid; +} + +export async function getDwnEndpoints() { +// selects DWN endpoints that are provided by default during the Web5 tech preview period +const serviceEndpointNodes = await getTechPreviewDwnEndpoints(); + +// generates key pairs used for authorization and encryption when interfacing with DWNs +const didOptions = await DidIonMethod.generateDwnOptions({ serviceEndpointNodes }); +return didOptions; +} + +export async function createSocialMediaAndCareerIdentity() { + const socialMediaIdentity = await agent.identityManager.create({ + name: 'SocialMedia', + didMethod: 'ion', + didOptions, + kms: 'local' + }); + + const careerIdentity = await agent.identityManager.create({ + name: 'Career', + didMethod: 'ion', + didOptions, + kms: 'local' + }); + + return { socialMediaIdentity, careerIdentity }; +} + +export async function connectIdentityToWeb5() { + const { web5 } = await Web5.connect({ + connectedDid: socialMediaIdentity.did, + agent, + }); + return web5; +} + +export async function connectToWeb5() { + const { web5 } = await Web5.connect({ + connectedDid: socialIdentity.did, + agent, + }); + return web5; +} \ No newline at end of file diff --git a/site/docs/web5/build/decentralized-web-nodes/using-identity-agents.mdx b/site/docs/web5/build/decentralized-web-nodes/using-identity-agents.mdx new file mode 100644 index 000000000..45529307e --- /dev/null +++ b/site/docs/web5/build/decentralized-web-nodes/using-identity-agents.mdx @@ -0,0 +1,94 @@ +--- +sidebar_position: 9 +--- + +import CodeSnippet from '@site/src/components/CodeSnippet'; + +# Using Identity Agents + +Identity Agents are a specialized type of [agent](https://developer.tbd.website/docs/web5/learn/agents/) that act as personal identity managers. Similar to how a password manager securely stores login credentials for different web apps, Identity Agents manage your [DIDs](https://developer.tbd.website/docs/web5/learn/decentralized-identifiers). + +By default, the `Web5.connect()` function generates a **new** identity; however, Identity Agents allow you to connect to a Web5 app with an **existing** identity. This is useful for maintaining a cohesive online presence. + +This guide will walk you through creating an Identity Agent as a standalone application to manage multiple user identities and connect to a **separate** Web5 application. + + +
+Prerequisites + +**Install the following packages** + +```bash +npm i @web5/dids @web5/identity-agent @web5/api +``` + +**Import the following modules** + +```js +import { DidIonMethod } from '@web5/dids'; +import { IdentityAgent } from '@web5/identity-agent'; +import { Web5, getTechPreviewDwnEndpoints } from '@web5/api'; +``` + +
+ +## Initialize the Agent +To create an Identity Agent, start by creating an instance of the `IdentityAgent`. + + + +## Prompt Users to Authenticate +In your Identity Agent app, include the code snippet below to prompt users to authenticate with a one-time passphrase for security purposes. + + + +## Connect DIDs to DWNs +To add [DWNs](https://developer.tbd.website/docs/web5/learn/decentralized-web-nodes/) as service endpoints to the DIDs that will be managed by the Identity Agent, you can add the endpoints as `didOptions`. + +Below is an example using TBD-hosted DWN endpoints: + + + + +
+Expected Output of didOptions + +```js +{ + keySet: { verificationMethodKeys: [ [Object], [Object] ] }, + services: [ + { + id: '#dwn', + serviceEndpoint: [Object], + type: 'DecentralizedWebNode' + } + ] +} +``` + +
+ +## Create Identities +Now that `didOptions` contains cryptographic key pairs and service endpoints, you can create as many identities as necessary. + +Each identity, linked to a unique DID, represents and compartmentalizes different aspects of a user. For example, a user can have an identity for social media interactions and another for professional engagements. + + + +:::note +* The `name` field serves as a friendly name for reference. +* The `kms` field stands for [Key Management Service](/docs/web5/build/decentralized-identifiers/key-management) and can securely store and manage the cryptographic keys associated with a DID. If you don't specify a particular key management service, such as AWS Key Management Service, Web5 will default to an in-memory key manager. +::: + +## Use your Identity in a Web5 Application +Once your Identity Agent is set up to manage multiple identities, you can use it to help you login to a Web5 application. Here's the typical workflow: + +1. The Web5 application may display a QR code or a deep link for login purposes. +2. Use the Identity Agent to scan the QR code or click the deep link to establish a connection between your Identity Agent and the Web5 application. +3. The Identity Agent then prompts you to select the identity you prefer to use for authentication. + +## Connect to Web5 +Finally, the Web5 application can use the DID linked to your identity, so that you can interact within the Web5 application as your chosen identity. + + + diff --git a/site/src/util/code-snippets-map.json b/site/src/util/code-snippets-map.json index 041c07ac6..5aa26ac00 100644 --- a/site/src/util/code-snippets-map.json +++ b/site/src/util/code-snippets-map.json @@ -46,6 +46,12 @@ "sendProtocolToRemoteDWNs": "const { protocol } = await web5.dwn.protocols.configure({\n message: {\n definition: protocolDefinition\n }\n});\n\n//immediately send protocol to user's remote DWNs\nconst {status} = await protocol.send(userDid);", "sendRecordToDWNOfRecipient": "const { record } = await web5.dwn.records.create({\n data: \"this record will be created but not saved to DWN\",\n store: false, //remove this line if you want to keep a copy of the record in the sender's DWN\n message: {\n dataFormat: 'text/plain'\n },\n});\n\n//send record to recipient's DWN\nconst {status} = await record.send(recipientDid);", "updateDwnRecord": "// Get the record\nconst { record } = await web5.dwn.records.read({\n message: {\n filter: {\n recordId: createdRecord.id\n }\n }\n});\n\n// Update the record\n// highlight-next-line\nconst {status} = await record.update({ data: \"Hello, I'm updated!\" });", + "createIdentityAgent": "const agent = await IdentityAgent.create();", + "authenticateIdentityAgent": "await agent.start({ passphrase: 'default-passphrase' });", + "getDwnEndpoints": "// selects DWN endpoints that are provided by default during the Web5 tech preview period\nconst serviceEndpointNodes = await getTechPreviewDwnEndpoints();\n\n// generates key pairs used for authorization and encryption when interfacing with DWNs\nconst didOptions = await DidIonMethod.generateDwnOptions({ serviceEndpointNodes });", + "createSocialMediaAndCareerIdentity": "const socialMediaIdentity = await agent.identityManager.create({\n name: 'SocialMedia',\n didMethod: 'ion',\n didOptions,\n kms: 'local'\n });\n\n const careerIdentity = await agent.identityManager.create({\n name: 'Career',\n didMethod: 'ion',\n didOptions,\n kms: 'local'\n });", + "connectIdentityToWeb5": "const { web5 } = await Web5.connect({\n connectedDid: socialMediaIdentity.did,\n agent,\n });", + "connectToWeb5": "const { web5 } = await Web5.connect({\n connectedDid: socialIdentity.did,\n agent,\n });", "createTextRecord": "const { record } = await web5.dwn.records.create({\n data: 'Hello, Web5!',\n message: {\n dataFormat: 'text/plain',\n },\n });", "createJsonRecord": "// Create a JSON record\nconst { record } = await web5.dwn.records.create({\n data: {\n content: \"Hello Web5\",\n description: \"Keep Building!\"\n },\n message: {\n dataFormat: 'application/json'\n }\n});", "uploadImage": "// Create a blob record\n async function upload(event) {\n const blob = new Blob(event.currentTarget.files, { type: \"image/png\" });\n const { record } = await web5.dwn.records.create({\n data: blob,\n message: {\n dataFormat: \"image/png\"\n }\n });\n \n }",