Explanatory Tutorial for websites to implement logging in with web3 credentials using the KILT Protocol.
This project demonstrates how to enable login using KILT Protocol. It is an educational project, everything is broken down to little steps and has explanations to ease your understanding.
During the login procedure, the user is required to present a credential. You can decide which credentials to accept. They could be issued by yourself or by other attesters.
If you require the credentials to be attested by yourself, the credentials become similar to membership cards. If you also accept credentials from other attesters, you open your system up to also accept other membership cards.
This means that users don't not need to setup an account and password just for your website. Additionally, this avoids third parties (usually powerful data-collecting companies) from tracking your interactions with your clients. There is no trace on the KILT blockchain about the interaction between you and your client.
In order for a dApp to support logging in with KILT Credentials, it needs:
- It's on-chain DID
- This DID is used so that the user knows the parties to whom they talk.
- Bind your DID to a specific domain.
- This prevents Man-in-the-Middle attacks.
- Also known as the well-known-did-configuration.
- A CType to request from the user
- The type of credentials the dApp considers valid.
- The CType defines the structure and semantics of the credential you accept.
- The issuer of this credentials is important. Anyone can issue a credential of a CType, but not everybody can be trusted as a source of truth.
- To be secure, your dApp should accept credentials coming from it's trusted attesters.
If you don't have some of the above, don't worry, we help you get them on the sections below.
After setting up and running this project locally on your computer, you will have representation of how the login process works. You are free to copy code from here, but we do encourage you to customize the code for your specific use case. Just keep in mind to follow the specifications to retain compatibility with the wallets/extensions.
If you are rather looking for a Plug-and-Play solution, we provide the openDID project which implements the OpenID-Connect implicit flow to authenticate your users. OpenDID substantially reduces the complexity of integration.
It is important, that the dApp and the user interact with information from the same chain. There is the KILT Test Chain aliased Peregrine. And there is the production KILT Chain aliased Spiritnet.
For a user to log in with KILT, it requires:
- A compatible wallet or extension.
-
The wallet is required to follow the KILT credential API specifications.
-
The Sporran Wallet is one example for such an extension.
- An on-chain DID
- If you are using Sporran, here is how to get a DID.
- A Credential that the dApp considers valid.
-
This means the credential has to (simultaneously) be:
- one of the cTypes accepted by the dApp.
- issued by one of the dApps trusted attesters.
- Clone repository and install all necessary modules.
- Define your environment variables
- Build a Domain Linkage Credential
- Start your dApp
This code makes use of a mix of different technologies. If you are not familiar with some of these, we recommend to first get an overview about them.
After cloning the repository, to install all required modules
- run:
yarn install
.
Before you can run this project, you need to setup your environment variables. This variables specify which blockchain we use and they hold the secrets for your DID and to sign JWTs. Defining them is part of the set up of your project.
The .env
-file should be on the root directory of this repository.
This file maybe hidden.
It is included on the .gitignore
list so that the secrets that are contained in the file never get pushed to GitHub.
There is a .env.example
-file that also list how your variables should be named.
The .env
-file should be added to the same level (directory) where .env.example
-file is found.
The following variables are required:
WSS_ADDRESS
= This is the websocket address of the RPC nodeFRONTEND_PORT
= This is the local Port on which your website (client-side) would be reachableBACKEND_PORT
= This is the local Port on which your server would be reachableDAPP_ACCOUNT_MNEMONIC
= This is the mnemonic of the Kilt account paying for all transactionsDAPP_DID_MNEMONIC
= This is the mnemonic of the Kilt DID that identifies your dAppDAPP_DID_URI
= This is the URI of the Kilt DID that identifies your dAppDAPP_NAME
= This should be a custom name for your dAppJWT_SIGNER_SECRET
= This is secret key (string) that signs the Json-Web-Tokens before saving them in the CookiesCTYPE_HASH
= This is the type of credential (CType) your dApp will request from users for login. If you want to specify more than one CType, you can do so by adding a '/' sign between themTRUSTED_ATTESTERS
= This is a list of attester DIDs (CSV, separated by ','). Only credentials issued by these attesters will be considered valid. If you are using more than one CType, indicate the groups of trusted attesters by respectively separating them with a '/' sign.REQUIRED_PROPERTIES
= This is a subset of CType properties (CSV, separated by ',') required to be exposed on credential presentation. If you are using more than one CType, indicate the groups of required properties by respectively separating them with a '/' sign.
This table provides information on recommended cTypes for SocialKYC credential issuance. SocialKYC is a platform that issues credentials based on user social media presence. The cType-hash is consistent across both Spiritnet and Peregrine to ensure interoperability and consistent verification across the KILT network by using universal identifiers stored on archive nodes.
did:kilt:4pnfkRn5UurBJTW92d9TaVLR2CqJdY4z5HPjrEbpGyBykare
did:kilt:4pehddkhEanexVTTzWAtrrfo2R7xPnePpuiJLC7shQU894aY
Here are the cTypes recommended for SocialKYC:
Title | CTYPE_HASH | SocialKYC on Spiritnet | SocialKYC on Peregrine | Properties |
---|---|---|---|---|
0x3291bb126e33b4862d421bfaa1d2f272e6cdfc4f96658988fbcffea8914bd9ac | prod. Spiritnet | prod. Peregrine | ||
0x47d04c42bdf7fdd3fc5a194bcaa367b2f4766a6b16ae3df628927656d818f420 | prod. Spiritnet | prod. Peregrine | ||
Discord | 0xd8c61a235204cb9e3c6acb1898d78880488846a7247d325b833243b46d923abe | prod. Spiritnet | prod. Peregrine | Discriminator, User ID, Username |
GitHub | 0xad52bd7a8bd8a52e03181a99d2743e00d0a5e96fdc0182626655fcf0c0a776d0 | prod. Spiritnet | prod. Peregrine | GitHubUsername, Username |
Twitch | 0x568ec5ffd7771c4677a5470771adcdea1ea4d6b566f060dc419ff133a0089d80 | prod. Spiritnet | prod. Peregrine | User ID, Username |
Telegram | 0xcef8f3fe5aa7379faea95327942fd77287e1c144e3f53243e55705f11e890a4c | prod. Spiritnet | prod. Peregrine | First name, Last name, User ID, Username |
YoutubeChannel | 0x329a2a5861ea63c250763e5e4c4d4a18fe4470a31e541365c7fb831e5432b940 | prod. Spiritnet | prod. Peregrine | Channel ID, Channel Name |
Note: We only recommend these cTypes and properties for SocialKYC verification. Users are advised to select the appropriate properties based on their verification needs.
Additionally, for access to a broader range of CType Hashes, consider visiting the CType Hub.
You have three different options:
-
Using
yarn environment
:There is a script to facilitate the generation of the environment variables inside of this repository. This script is called
./scripts/genesisEnvironmentVariables.ts
.- You can execute it by running
yarn environment
.
Setting up your environment with this script is a step by step process. You will need to run the script repeatedly and follow the instructions that it provides, depending on your project's state. After running this script each time, you need to manually copy the output and save it on the
.env
-file . - You can execute it by running
-
Using the kilt-distillery-cli
This is a Command Line Interface tool that can help you obtaining this variables and also does other common tasks (unrelated to this project). The distillery uses the same key derivation as this repository, which means that it is highly compatible.
-
Without help:
If you are a pro, you could defined and generate them externally and add them to the
.env
-file. You would probably have to modify thegenerateKeyPairs.ts
files (on scripts and backend) to match your key derivation though.
After having all your environment variables, execute the following to build a Domain Linkage Credential:
yarn run did-configuration
.
If you want know more about this, check out the Identity Foundation Documentation on Well-Known DID Configuration. Our dApp documentation also have a section about this.
After having all your environment variables and your well-known DID configuration run:
yarn start
.
After having set up the whole project, when the website is up and running, the login process can take place. Each user that wants to login would trigger the process that is displayed below. The process consist of multiple HTTP(S) queries, including extension-api's messages. Interactions are between the client (browser), the extension/wallet and the server (backend).
A user could trigger the whole process with just one click on the website, for example "Login with extension X" or "Login with extension Y".
On this example project, several (2-3) interactions are need because it is broken down into little steps. We encourage you to open and read the browser's and backend's consoles. This should help you understand what is happening on each step, which function is responsible for what, how did the cookies changed.
+-----------+ +---------+ +---------+
| Extension | | Browser | | Server |
+-----------+ +---------+ +---------+
| | ------------------------\ |
| |-| User visits web3login | |
| | |-----------------------| |
| | --------------------------------------\ |
| |-| User chooses an Extension X | |
| | | and clicks on the "Connect" button. | |
| | |-------------------------------------| |
| | |
| please allow use on this page | |
|<-------------------------------------------------------------| |
| -------------------------------------------------------\ | |
|-| Only the "Extension X" pops up, only the first time. | | |
| |------------------------------------------------------| | |
| ---------------------------------------\ | |
|-| The Domain Linkage Credentials under | | |
| | .well-known/did-configuration.json | | |
| | is verified. | | |
| |--------------------------------------| | |
| | |
| User granted access | |
|------------------------------------------------------------->| |
| | |
| | GET /api/session/start |
| |-------------------------------------------------------------------------->|
| | |
| | 200 OK |
| | set-cookie: sessionJWT={dAppName, dAppEncryptionKeyUri, challenge} |
| | {dAppName, dAppEncryptionKeyUri, challenge} |
| |<--------------------------------------------------------------------------|
| | |
| startSession(dAppName, dAppEncryptionKeyUri, challenge) | |
|<-------------------------------------------------------------| |
| | |
| {encryptionKeyId, encryptedChallenge, nonce} | |
|------------------------------------------------------------->| |
| | |
| | POST /api/session/verify |
| | Cookie: sessionJWT={dAppName, dAppEncryptionKeyUri, challenge} |
| | {encryptionKeyId, encryptedChallenge, nonce} |
| |-------------------------------------------------------------------------->|
| | ----------------------------------------------------\ |
| | | Verify sessionJWT. |-|
| | | Decrypt challenge using nonce and encryptionKeyId | |
| | | Verify Extension Session: | |
| | | Assert that jwt-challenge (our) | |
| | | and decrypted-challenge (theirs) match. | |
| | |---------------------------------------------------| |
| | |
| | 200 OK |
| | set-cookie: sessionJWT={{dAppName, dAppEncryptionKeyUri, challenge}, |
| | {encryptionKeyId, encryptedChallenge, nonce}} |
| |<--------------------------------------------------------------------------|
| ---------------------------------------------\ | |
| | Server-Extension-Session established ✉️ ⛓️ |-| |
| |--------------------------------------------| | |
| | -----------------------\ |
| |-| User clicks on Login | |
| | |----------------------| |
| | |
| | GET /api/credential/login/request |
| | Cookie: sessionJWT |
| |-------------------------------------------------------------------------->|
| | ------------------------------------------------------------------\ |
| | | The Server is asking for a Credential of a cType from the User. |-|
| | |-----------------------------------------------------------------| |
| | |
| | 200 OK |
| | set-cookie: credentialJWT={challengeOnRequest} |
| | KiltMsg{request-credential} |
| |<--------------------------------------------------------------------------|
| | |
| send(KiltMsg{request-credential}) | |
|<-------------------------------------------------------------| |
| -----------------------------------\ | |
|-| User approves the request | | |
| | and selects credential to share. | | |
| |----------------------------------| | |
| | |
| KiltMsg{submit-credential} | |
|------------------------------------------------------------->| |
| | |
| | Post /api/credential/login/submit |
| | Cookie: credentialJWT |
| | KiltMsg{submit-credential} |
| |-------------------------------------------------------------------------->|
| | ---------------------------------------------------\ |
| | | Verify the credential. |-|
| | | Note the DID inside the credential. | |
| | | If verification was successful, | |
| | | DID was authenticated with provided credentials. | |
| | |--------------------------------------------------| |
| | ---------------------------------------------------\ |
| | | The login with credential process was completed. |-|
| | | An authentication token is given to the user. | |
| | | It's all like web2 from here on. | |
| | |--------------------------------------------------| |
| | |
| | 200 OK |
| | set-cookie: accessJWT{authenticationToken} |
| |<--------------------------------------------------------------------------|
| | |
We have deployed this demo dApp as well, making it accessible online. This allows the developers to compare their results. It also makes it possible for non-coders to it check out.
Reach the version connected to the KILT Production Chain aliased Spiritnet:
- under: https://login-demo.kilt.io/
Reach the version connected to the KILT Development Chain aliased Peregrine:
Running the app locally on your computer doesn't require this step. It's only necessary when you plan to deploy the app, not for the tutorial.
To obtain a production-ready version of the app, you can compile both the frontend and backend code into Docker images. For this, after having all your environment variables and your well-known DID configuration:
- run
yarn build
.