{"id":26121919,"url":"https://github.com/ipfs-shipyard/workshop-idm-chat-dapp","last_synced_at":"2025-10-07T02:18:11.314Z","repository":{"id":44850577,"uuid":"189904160","full_name":"ipfs-shipyard/workshop-idm-chat-dapp","owner":"ipfs-shipyard","description":"The IDM mini-chat DApp workshop","archived":false,"fork":false,"pushed_at":"2025-05-01T07:33:13.000Z","size":1228,"stargazers_count":10,"open_issues_count":2,"forks_count":0,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-01T08:29:44.015Z","etag":null,"topics":["authenticate","chat","dapp","idm","sign","workshop"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ipfs-shipyard.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-02T23:08:38.000Z","updated_at":"2025-05-01T07:33:17.000Z","dependencies_parsed_at":"2022-09-24T23:22:39.586Z","dependency_job_id":null,"html_url":"https://github.com/ipfs-shipyard/workshop-idm-chat-dapp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ipfs-shipyard/workshop-idm-chat-dapp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipfs-shipyard%2Fworkshop-idm-chat-dapp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipfs-shipyard%2Fworkshop-idm-chat-dapp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipfs-shipyard%2Fworkshop-idm-chat-dapp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipfs-shipyard%2Fworkshop-idm-chat-dapp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ipfs-shipyard","download_url":"https://codeload.github.com/ipfs-shipyard/workshop-idm-chat-dapp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipfs-shipyard%2Fworkshop-idm-chat-dapp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278708004,"owners_count":26031932,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authenticate","chat","dapp","idm","sign","workshop"],"created_at":"2025-03-10T14:37:26.129Z","updated_at":"2025-10-07T02:18:11.294Z","avatar_url":"https://github.com/ipfs-shipyard.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# IDM Chat app workshop\n\nThis walk-through will guide you into the process of integrating IDM to provide authentication and signing into a simple decentralized chat app. There're [introductory slides](https://docs.google.com/presentation/d/1HbydOI0w-T_FY23zCACAyHmzDq1ZvyG2tklpPSm6OQQ/edit?usp=sharing) that talk about the underlying standards IDM uses, such as DIDs and Verifiable Credentials.\n\nThe project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).\n\n## Walk-through\n\nFollow each step below to complete the workshop. At any time, you may check the final application in the [`with-idm`](https://github.com/ipfs-shipyard/workshop-idm-chat-dapp/compare/master...with-idm) branch if you are stuck or running into issues.\n\n1. [Prerequisites](#1-prerequisites)\n1. [Installation](#2-installation)\n1. [Understanding the chat app](#3-understanding-the-chat-app)\n1. [Setting up `idm-client` in the project](#4-setting-up-idm-client-in-the-project)\n1. [Integrate login \u0026 logout](#5-integrate-login--logout)\n1. [Integrate signing and verification of signatures](#6-integrate-signing-and-verification-of-signatures)\n    1. [Signing with the device key](#61-signing-with-the-device-key)\n\n### 1. Prerequisites\n\n- [`git`](https://git-scm.com/) installed on your machine.\n- [Node.js](https://nodejs.org/download/) `^10.16.0` or greater installed on your machine.\n\n    \u003e ⚠️ Node `v12` is not yet supported as some libraries do not compile correctly.\n- A modern browser, such as [Chrome](https://www.google.com/chrome) or [Firefox](https://www.mozilla.org/firefox/new/).\n- A code editor, such as [Visual Code](https://code.visualstudio.com/), [Atom](https://atom.io/) or [Sublime](https://www.sublimetext.com/).\n\n### 2. Installation\n\nSimply clone this repository, install the dependencies and run the app locally:\n\n```sh\n$ git clone git@github.com:ipfs-shipyard/workshop-idm-chat-dapp.git\n$ cd workshop-idm-chat-dapp \u0026\u0026 npm i \u0026\u0026 npm start\n```\n\n\u003e 🙏 These commands may take a while, please be patient.\n\n### 3. Understanding the chat app\n\nThe [`index.js`](src/index.js) file is the main entry point. Its responsibility is to setup the app, initialize an IPFS node for the real-time chat, and to render the root [`\u003cBoot\u003e`](src/Boot.js) \u0026 [`\u003cApp\u003e`](src/App.js) React components.\n\nThe `\u003cBoot\u003e` component will display a loading icon while the setup process is inflight and an error message if the setup process failed. The `\u003cApp\u003e` component will only be rendered once the setup process finishes successfully. It also connects to all the [`stores`](src/stores) so that parts of the app will re-render automatically whenever these stores' change state.\n\nFeel free to peek at the rest of the React [`components`](src/components), but we will be mainly focusing on the [`stores`](src/stores) during the workshop.\n\nThere are two stores: the [`userStore`](src/stores/user.js) and the [`roomStore`](src/stores/room.js). As the names suggest, the `userStore` manages the current logged in user and exports functions to `login()` and `logout()`, while the `roomStore` manages the room messages and peers and exports functions to `sendMessage()` and `verifyMessage()`. These functions contain mocks that we will be re-implementing.\n\nThe chat app should be running on `http://localhost:3500`, try it out! 🚀\n\n\u003e ℹ️ Most of the code was kept simple so that it's easy to understand. In this example, we avoided using react hooks, functional components and state management libraries such as Redux.\n\n### 4. Setting up `idm-client` in the project\n\nWe need to install and setup a IDM Client, in order to interact with IDM based wallets. You may skip the install command below as both are already installed, but for reference you would type:\n\n```sh\nnpm i idm-client idm-bridge-postmsg\n```\n\nThe [`idm-client`](https://github.com/ipfs-shipyard/js-idm-client) package is the reference implementation of the IDM Client in JavaScript. But IDM Clients alone can't discover nor communicate with IDM Wallets. For that they need to be configured with a bridge. That's why we also installed the [`idm-bridge-postmsg`](https://github.com/ipfs-shipyard/js-idm-bridge-postmsg) which internally uses the [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) API.\n\nNow lets setup these libraries shall we? Lets open [`index.js`](src/index.js) in your favorite editor and add the following lines:\n\n```js\n// src/index.js\nimport createIdmClient from 'idm-client';\nimport { createClientSide } from 'idm-bridge-postmsg';\n\n// ...\n\nconst WALLET_URL = 'https://demo.nomios.io';\nconst APP = {\n    name: 'Example Chat App',\n    homepageUrl: window.location.origin,\n    iconUrl: `${window.location.href}favicon.ico`,\n};\n```\n\nAbove, we are importing both libraries as well as defining some constants. The `WALLET_URL` constant is where the IDM Wallet is running, which in this case is Nomios. The `APP` constant holds the app details that will be shown when prompting the user to authenticate and to sign.\n\nNow, lets initialize the actual IDM Client:\n\n```js\n// src/index.js\n// ...\n\nconst setup = async () =\u003e {\n    // ...\n\n    // Setup IDM Client\n    const idmBridge = await createClientSide(WALLET_URL);\n    const idmClient = await createIdmClient(APP, idmBridge, { ipfs });\n\n    // Finally configure our stores\n    await configure({ ipfs, idmClient });\n});\n```\n\nBefore creating the actual `idmClient`, we are initializing the `idmBridge` based on the postMessage API and passing `WALLET_URL` to its factory. The `idmClient` is then created using the `APP` details, the `idmBridge` and the `ipfs` node we already had in place. That IPFS node will be used by the IDM Client to resolve [DID-Documents](https://w3c-ccg.github.io/did-spec/#did-documents) based on [IPID](https://did-ipid.github.io/ipid-did-method/). Finally, the created `idmClient` is passed to the `configure()` function so that our stores may use it internally.\n\n\u003e ⚠️ You must keep the Nomios wallet open ([https://demo.nomios.io](https://demo.nomios.io)) at all times. This limitation will be overcome in a later release by leveraging Service Workers.\n\n\u003e ℹ️ In the future, we will automatically discover wallets without having to hardcode their URLs.\n\n### 5. Integrate login \u0026 logout\n\nAs previously stated, the [`userStore`](src/stores/user.js) has two partially mocked functions that we need to work on. Lets start by storing a reference to the `idmClient` and listening to `onSessionChange` events:\n\n```js\n// src/stores/user.js\n// ...\n\nlet idmClient;\n\nexport const configure = async (params) =\u003e {\n    idmClient = params.idmClient;\n\n    idmClient.onSessionChange((session) =\u003e {\n        state = {\n            ...state,\n            currentUser: session ? session.profileDetails : undefined,\n        };\n\n        onChange.dispatch(state);\n    });\n};\n```\n\nThe `onSessionChange` callback we registered is called whenever the underlying session changes. This way we react not only when we login \u0026 logout, but also if the app (and its corresponding session) gets revoked by the user.\n\nNow that we have a reference to the `idmClient`, lets use it in the `login()` and `logout()` functions:\n\n```js\n// src/stores/user.js\n// ...\n\nconst store = {\n    // ...\n\n    login: async () =\u003e {\n        const session = await idmClient.authenticate();\n\n        console.log('Logged in!', session);\n    },\n\n    logout: async () =\u003e {\n        await idmClient.unauthenticate();\n\n        console.log('Logged out!');\n    },\n\n    // ...\n};\n```\n\nThe `login()` function now calls `idmClient.authenticate()`, which prompts the user to consent sending its [DID](https://w3c-ccg.github.io/did-spec/) and profile details to the app. If the user accepts, a unique session between the app and the wallet will be created. The returned `session` object contains the user DID and profile, among other fields. The profile may be one of the following schema.org types: [Person](https://schema.org/Person), [Organization](https://schema.org/Organization) or [Thing](https://schema.org/Thing).\n\nNote that we no longer need to update the store's `state` nor dispatch an `onChange` event as the `idmClient` will call the `onSessionChange` callback we registered ealier.\n\nWith just these small changes, we should be able to use the Nomios wallet to login to the app \u0026 logout from the app. If you haven't created your identity yet, please create it in Nomios.\n\nThere's an issue though: if you refresh the app, you will be logged out 😭. Lets fix that by adding a check at `configure()` right before the line where we subscribe to `onSessionChange`:\n\n```js\n// src/stores/user.js\nexport const configure = async (params) =\u003e {\n    // ...\n\n    if (idmClient.isAuthenticated()) {\n        state = {\n            ...state,\n            currentUser: idmClient.getSession().profileDetails,\n        };\n    }\n\n    // ...\n};\n```\n\nBy leveraging `idmClient.isAuthenticated()` and `idmClient.getSession()`, we are now able to resume a previous session successfully 💪.\n\n### 6. Integrate signing and verification of signatures\n\nThe final part we are missing is to guarantee that messages can be cryptographically verified by others. This will ensure the authenticity of messages by checking if they were made by one of the public keys listed in the [DID-Document](https://w3c-ccg.github.io/did-spec/#did-documents).\n\n\u003e ⚠️ Because DIDs allow for \"self-sovereign\" digital identity, someone could try to impersonate others by creating a fake profiles. DIDs begin by being \"trustless\" in the sense that they don't directly provide meaningful identity attributes. But trust between DID-identified peers can be built up through the exchange of [Verifiable Credentials](https://www.w3.org/TR/verifiable-claims-data-model/) - credentials about identity attributes that include cryptographic proof. These proofs can be verified by reference to the issuer's DID and DID-Document.\n\n\u003e ℹ️ Nomios will allow users to self-sign Verifiable Credentials proving they own certain profiles on social networks, similar to how [Keybase does](https://keybase.io/). As of today, many people trust the mainstream social networks, such as Facebook and Twitter, and identities may use them to post cryptographic proofs that link their profiles to a hash of their DID.\n\nAs mentioned earlier, the [`roomStore`](src/stores/room.js) has two partially mocked functions that we need to work on. Lets start by storing a reference to the `idmClient` in the `configure()` function, similar to what we did before:\n\n```js\n// src/stores/room.js\nlet idmClient;\n\nexport const configure = async (params) =\u003e {\n    idmClient = params.idmClient;\n\n    // ...\n};\n```\n\nNow that we have a reference to the `idmClient`, lets use it in the `sendMessage()` and `verifyMessage()` functions.\n\n```js\n// src/stores/room.js\nconst store = {\n    // ...\n\n    sendMessage: async (text) =\u003e {\n        // ...\n\n        message.signature = await idmClient.sign(message);\n\n        // ...\n    },\n\n    verifyMessage: async (message) =\u003e {\n        // ...\n\n        const result = await idmClient.verifySignature(originalMessage, signature);\n\n        // ...\n    },\n};\n```\n\nThat was easy huh? Now go test it, hurry!\n\nIf you want to know more about the signing and verifications process, you may read the Motivation section of the [idm-signatures](https://github.com/ipfs-shipyard/js-idm-signatures#motivation) repository.\n\n#### 6.1. Signing with the device key\n\nThe previous signing example was made using the session private key. This allows for non-intrusive signing use-cases where you do not want to prompt the user. Could you imagine using a chat app where you were prompted every-time a new message was typed? I certainly couldn't...\n\nThe trade-off here is that if someone gets access to the physical device and is able to bypass the built-in OS lock-screen (e.g.: by coercion), that person will see the raw session private keys because they are unencrypted. Anyone verifying signatures with those compromised session keys will see them as valid until the DID owner revokes that device from another IDM Wallet. Revoking a device key will automatically revoke all session keys because all session keys are children of device keys.\n\nThere are use-cases where you may want a higher level of security, such as when deleting a chat room. In those scenarios, you may request signing with the device private key which is stored encrypted within the IDM Wallet.\n\nBut we are not going to implement a \"Delete room\" feature in our app. Instead, we will be doing something easier but cooler 😎: lets sign with the device whenever the message's text contains the word \"IPFS\".\n\n```js\n// src/stores/room.js\nconst store = {\n    // ...\n\n    sendMessage: async (text) =\u003e {\n        // ...\n\n        message.signature = await idmClient.sign(message, {\n            signWith: /\\bipfs\\b/i.test(message.text) ? 'device' : 'session',\n        });\n\n        // ...\n    },\n};\n```\n\nSee how we defined the `signWith` option in relation to the IPFS word been present? That should do it!\n\n## Interested in knowing more?\n\nWhile IDM and Nomios are still in their infancy, this workshop is meant to showcase its potential and commitment to open-standards, such as [DIDs](https://w3c-ccg.github.io/did-spec) and [Verifiable Credentials](https://www.w3.org/TR/verifiable-claims-data-model/).\n\nHere are some references if you want to know more:\n\n- Project management repository on GitHub - https://github.com/ipfs-shipyard/pm-idm\n- IDM Concept document - https://github.com/ipfs-shipyard/pm-idm/blob/master/docs/idm-concept.md\n- IDM Specification document - https://github.com/ipfs-shipyard/pm-idm/blob/master/docs/idm-spec.md\n- IDM \u0026 Nomios codebase - https://github.com/ipfs-shipyard/pm-idm#codebase\n\nIf you are interested in helping us or even just tracking progress, you may do so via:\n\n- Subscribing to the Nomios newsletter at http://nomios.io\n- Chatting with us on `#ipfs` and `#ipfs-identity` IRC channels on freenode.net\n- Attending our bi-weekly progress calls - https://github.com/ipfs-shipyard/pm-idm/issues?q=progress+label%3Aprogress-call\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fipfs-shipyard%2Fworkshop-idm-chat-dapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fipfs-shipyard%2Fworkshop-idm-chat-dapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fipfs-shipyard%2Fworkshop-idm-chat-dapp/lists"}