diff --git a/docs/develop/02_chain/04_fullnode.md b/docs/develop/02_chain/04_fullnode.md index 39810ced4..d056efcc4 100644 --- a/docs/develop/02_chain/04_fullnode.md +++ b/docs/develop/02_chain/04_fullnode.md @@ -41,7 +41,7 @@ This can either be `peregrine` or `spiritnet`. Hence, to start a full node for the Spiritnet network, the parameter would be `--chain=spiritnet`. Unfortunately, there is no hardcoded chain spec for the Peregrine network, so the full path of the chainspec file must be provided `--chain=/node/dev-specs/kilt-parachain/peregrine-kilt.json`. -Please refer to the [KILT node repository](https://github.com/KILTprotocol/kilt-node/blob/develop/dev-specs/kilt-parachain/peregrine-kilt.json) or the [Docker image](https://hub.docker.com/r/kiltprotocol/kilt-node/tags) for more information. +Please refer to the [KILT node repository](https://github.com/KILTprotocol/kilt-node/blob/master/dev-specs/kilt-parachain/peregrine-kilt.json) or the [Docker image](https://hub.docker.com/r/kiltprotocol/kilt-node/tags) for more information. ### Specify the Blockchain Storage Path diff --git a/docs/develop/08_opendid/01_overview.md b/docs/develop/08_opendid/01_overview.md new file mode 100644 index 000000000..f599db770 --- /dev/null +++ b/docs/develop/08_opendid/01_overview.md @@ -0,0 +1,40 @@ +--- +id: what-is-opendid +title: Overview +--- + +[OpenDID](https://github.com/KILTprotocol/opendid) is an OpenID Provider implementation capable of authenticating users through their [Decentralized Identifier (DID)](../../concepts/02_did.md) and Verifiable Credentials. + +It follows the [OpenID Connect 1.0 Specification](https://openid.net/specs/openid-connect-core-1_0.html#Introduction) and acts as a bridge between the decentralized identity world and the centralized authentication world supporting both the implicit and Authorization Code Flow. + +A major use of OpenDID is Single Sign-On (SSO), which allows users to use the same DID and credentials to sign into multiple platforms and web services. For instance, by adding a "Sign in with KILT" button to a webpage. + +Although integrating that functionality into a webpage is relatively simple, configuring and running OpenDID is more involved. + +:::info + +To learn more about the flow of OpenDID, see the [OpenDID Flow](./02_opendid_flow.md) documentation. + +::: + +## Project container structure + +The project consist of multiple parts that supplement and interact with each other all shipped as Docker containers and released to Docker Hub. + +### opendid-setup container + +The OpenDID Service needs configuration to run, which you can apply using this +container. +For example, it requires a DID to establish a session with an identity wallet. +This container creates a DID and the necessary configuration by providing an account with enough funds. + +Learn more in the [run setup container documentation](./03_opendid_service.md#run-setup-container). + +### kiltprotocol/opendid container + +This container [runs the OpenDID Service](./03_opendid_service.md#run-the-service), both the OpenDID front and back end. +This container requires the configuration file created from the `opendid-setup` container. + +### kiltprotocol/opendid-demo + +This container is a [web app demo](./05_demo_project.md), including front and back end services to demonstrate the use of OpenDID. diff --git a/docs/develop/08_opendid/02_opendid_flow.md b/docs/develop/08_opendid/02_opendid_flow.md new file mode 100644 index 000000000..49dc3e272 --- /dev/null +++ b/docs/develop/08_opendid/02_opendid_flow.md @@ -0,0 +1,74 @@ +--- +id: flow +title: OpenDID Flow +--- + +This guide explains the internal workings of OpenDID. +Understanding this flow is helpful for setting up and configuring an OpenDID Service but less important if you only need to integrate it in an application. + +OpenDID includes interactions between multiple apps to authenticate and authorize users. +Common use cases include the following: + +- Web app front end (app that includes the login button, for example, the demo app) +- Web app back end +- OpenDID front end +- OpenDID back end +- Identity wallet that follows [the Credential API spec](https://github.com/KILTprotocol/spec-ext-credential-api) (typically a browser extension, for example, [Sporran](https://www.sporran.org/)) + +The following steps outline the interactions necessary to implement [the implicit flow](https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth): + +1. The user clicks the login button on the *web app front end*. +2. The *web app front end* redirects the user to the *OpenDID front end*. +3. The user chooses what wallet to authenticate with. +4. The *OpenDID back end* establishes a secure session with the *identity wallet*. +5. The *OpenDID back end* optionally requests a credential that implements a specific CType. +6. The *identity wallet* provides the *OpenDID back end* with the requested credential, after authenticating the DID holder. +7. The *OpenDID back end* returns a `id_token` as a JSON web token (JWT) to the *OpenDID front end*. +8. *OpenDID front end* redirects the user back to a specific `redirect_url` on the *web app front end* including the `id_token`. +9. The *web app front end* detects the `id_token` and sends it to the *web app back end*. +10. The *web app back end* verifies the `id_token` and ensures the validity of the credential. + +The following sequence diagram summarizes the flow: + +```mermaid +sequenceDiagram + +participant AB as WebApp Backend +participant AF as WebApp Frontend +participant OF as OpenDID Frontend +participant OB as OpenDID Backend +participant IW as Identity Wallet + +AF->>OF: (1, 2) Authorize (redirect_uri: /callback) +OF->>OF: (3) Pick Identity Wallet +critical (4) Key Exchange +OF->>OB: GET Challenge +OB-->>OF: Challenge +OF->>IW: Start Session +IW-->>OF: Encrypted Challenge +OF->>OB: POST Challenge +OB-->>OF: OK +end + +critical Authenticate +OF->>OB: (5) GET Credential Requirements +OB-->>OF: Credential Requirements +OF->>IW: (6) Request Credential +IW->>IW: Authenticate User +IW->>OF: Credential +OF->>OB: POST Credential +OB->>OB: Verify Credential +OB->>OF: (7) `id_token`) +end + +OF->>AF: redirect to /callback with `id_token` +AF->>AB: (8) `id_token` +AB->>AB: (9) verify `id_token` +AB->>AF: (10) Access granted. + +``` + +:::info +Although this example describes the implicit flow, [the authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth) is similar. +Instead of returning an `id_token` directly, the OpenDID service instead returns a `code` to exchange for an `id_token` using the `token` endpoint. +::: diff --git a/docs/develop/08_opendid/03_opendid_service.md b/docs/develop/08_opendid/03_opendid_service.md new file mode 100644 index 000000000..ccb6213f3 --- /dev/null +++ b/docs/develop/08_opendid/03_opendid_service.md @@ -0,0 +1,116 @@ +--- +id: opendid_service +title: Run OpenDID Service +--- + + + +## Configuration + +Running the OpenDID service requires some configuration and a KILT DID. +The DID establishes a secure session with an identity wallet using a key agreement key of type `X25519KeyAgreementKey2019` included in the DID Document generated by the setup container. + +OpenDID serves a [well-known DID configuration](https://identity.foundation/.well-known/resources/did-configuration/), which the identity wallet uses to ensure that the domain is linked to the specified DID. + +### Run setup container + +Before running the `opendid-setup` container, set two environment variables: + +1. `SEED` to provide an account with funds (minimum of 3 KILT) for the DID generation. + + ```bash + export SEED="dont try this seed its completely made up for this nice example" + ``` + +2. `ENDPOINT` + + Set to "spiritnet" if the account is on the spiritnet production network. + + ```bash + export ENDPOINT="spiritnet" + ``` + + Set to "peregrine" if the account is on the peregrine test network. + + ```bash + export ENDPOINT="peregrine" + ``` + + Then run the setup with the following command: + + ```bash + docker run --rm -it -e "ENDPOINT=${ENDPOINT}" -v $(pwd):/data docker.io/kiltprotocol/opendid-setup:latest "${SEED}" + ``` + +The command generates a set of new mnemonics and then derives a DID from them and generates multiple files into the current directory: + +1. `config.yaml` The configuration file used by the OpenDID service. + + :::warning + You only need the `config.yaml` to run the OpenDID service. + This file includes the generated mnemonic and secret keys and you should protect it from unauthorized access. + ::: + +2. `did-secrets.json` This file contains the public and secret keys in the DID Document. + + :::warning + Keep a secure backup of this file as it contains all the secret keys. + ::: + +3. `did-document.json` contains the DID Document generated by this setup. + +The container generates sensible defaults in the `config.yaml` file, but here are some values you might want to change: + +- Set `production` to true, this only allows secure connections. +- Set the `WellKnownDid` > `origin`, which should match the host running the OpenDID service. +- Set the keys used for JWT issuance in the `jwt` section. +- The `client` section, including: + + - The client ID as a key (The default is: `example-client`). + - The `requirements` section, including: + - What CTypes are required for authentication. + - The trusted attesters as an address (The default is for the [SocialKYC attester](https://socialkyc.io/)). + + :::note info + + The generated default `config.yaml` requires an [email credential](https://test.ctypehub.galaniprojects.de/ctype/kilt:ctype:0x3291bb126e33b4862d421bfaa1d2f272e6cdfc4f96658988fbcffea8914bd9ac) issued by an attester. + + ::: + + - What `redirect_url`s the service accepts (The default is `http://localhost:1606/callback.html` for the demo project). + - The `clientSecret` is optional but recommended. If you use the authorization code flow, the `token` endpoint requires it. + +## Run the service + +When you've made changes to the `config.yaml` file, you can run the OpenDID service. + +1. Specify the runtime through the `RUNTIME` environment variable: + + Set to `"spiritnet"` for production KILT + + ```bash + export RUNTIME="spiritnet" + ``` + + Set to `"peregrine"` for the KILT test net. + + ```bash + export RUNTIME="peregrine" + ``` + +2. Run the `docker.io/kiltprotocol/opendid` docker image. + + ```bash + docker run -d --rm \ + -v $(pwd)/config.yaml:/app/config.yaml \ + -v $(pwd)/checks:/app/checks \ + -e "RUNTIME=${RUNTIME}" \ + -p 3001:3001 \ + docker.io/kiltprotocol/opendid:latest + ``` + +3. Open the login page at _http://localhost:3001_. + +## Next steps + +With configuration in place and a service running, next you need to [integrate OpenDID into an application](./04_integrate_opendid.md) so that a user can use the login page. diff --git a/docs/develop/08_opendid/04_integrate_opendid.md b/docs/develop/08_opendid/04_integrate_opendid.md new file mode 100644 index 000000000..6757cee95 --- /dev/null +++ b/docs/develop/08_opendid/04_integrate_opendid.md @@ -0,0 +1,116 @@ +--- +id: integrate_opendid +title: Integrate OpenDID +--- + +OpenDID follows the [OpenID Connect 1.0 Specification](https://openid.net/specs/openid-connect-core-1_0.html#Introduction) and implements both the [implicit flow](https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowSteps) +and the [authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth). +Read the [demo project guide](05_demo_project.md) for an example of integrating OpenDID. + +## Authorization code flow + +Initiate the flow by redirecting to the **GET** `/api/v1/authorize` endpoint on the OpenDID service and setting the following query URL-encoded parameters: + +- `response_type`: set value to `code` to indicate Authorization Code Flow. +- `client_id`: The client ID set in the `config.yaml` file. +- `redirect_uri`: OpenDID redirects to this URL after authentication. +- `scope`: set value to `openid`. +- `state`: set to a secure random number. +- `nonce`: optional value, set to a secure random number. + +**Example**: + +``` +GET /api/v1/authorize? + response_type=code& + client_id=example-client& + redirect_uri=http://localhost:1606/callback.html& + scope=openid& + state=rkw49cbvd4azu5dsln1xbl& + nonce=vedur4om49ei8w91jt7wt HTTP/1.1 +``` + +After successful authentication, the OpenDID service redirects back to the provided `redirect_uri` with `code` and `state` query parameters. + +**Example**: + +``` +/callback.html? + code=lwDS1ZpQBwR4Vdm53_L8bWpUJ1mx9A0mA_-86dubTqzqzwGazx1RyLX4Z_qf& + state=rkw49cbvd4azu5dsln1xbl +``` + +You can retrieve the `id_token` by calling the **POST** `/api/v1/token` and providing the following values in the form serialization: + +- `code`: code value returned from `authorize`. +- `grant_type`: set value to `authorization_code`. +- `redirect_uri`: the same `redirect_uri` used in `authorize`. +- `client_id`: the client ID set in the `config.yaml` file. +- `client_secret`: the client secret value set in the `config.yaml` file. + +**Example**: + +``` +POST /api/v1/token HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +code=lwDS1ZpQBwR4Vdm53_L8bWpUJ1mx9A0mA_-86dubTqzqzwGazx1RyLX4Z_qf& +grant_type=authorization_code& +redirect_uri=http%3A%2F%2Flocalhost%3A1606%2Fcallback.html& +client_id=example-client& +client_secret=insecure_client_secret +``` + +The OpenDID service returns the `id_token` in the response body serialized as a JSON object. + +```json +{ + "access_token": "SsFhhSBMWsLeDMxVUVGreKARNwYxMZtGFfBr0-ZiH6iondSmwPRvQDqkG6Fh", + "token_type": "bearer", + "refresh_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWQ6a2lsdDo0b0VkNENVV3RwbkxUVnZENVBFd2lMUmlqMWdzQmprS1JMbVpES2lCOEdqN2I2V0wiLCJ3M24iOiJjdXN0b20iLCJleHAiOjE3MTY4MTYwNjQsImlhdCI6MTcxNjgxNTQ2NCwiaXNzIjoiZGlkOmtpbHQ6NHJzQkE3dEQ1S1E4TDlXSGpGallRdUhrTWtha2NmSGRDNUNhUVVjVXh5VWpEVkhBIiwiYXVkIjoiYXV0aGVudGljYXRpb24iLCJwcm8iOnsiRW1haWwiOiJhYmR1bEBraWx0LmlvIn0sIm5vbmNlIjoidmVkdXI0b200OWVpOHc5MWp0N3d0In0.yOmE_9jWKcAu8LpjVx7IsFyOOvlKbgo2oC4Imf-qrLY", + "id_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWQ6a2lsdDo0b0VkNENVV3RwbkxUVnZENVBFd2lMUmlqMWdzQmprS1JMbVpES2lCOEdqN2I2V0wiLCJ3M24iOiJjdXN0b20iLCJleHAiOjE3MTY4MTU1MjQsImlhdCI6MTcxNjgxNTQ2NCwiaXNzIjoiZGlkOmtpbHQ6NHJzQkE3dEQ1S1E4TDlXSGpGallRdUhrTWtha2NmSGRDNUNhUVVjVXh5VWpEVkhBIiwiYXVkIjoiYXBwbGljYXRpb24iLCJwcm8iOnsiRW1haWwiOiJhYmR1bEBraWx0LmlvIn0sIm5vbmNlIjoidmVkdXI0b200OWVpOHc5MWp0N3d0In0.YlRE9EGnSExQCb5m2iy4__58PZJlZdCZMsSvsuW4oj8" +} +``` + +:::note +In full-stack applications, calling the `token` endpoint is usually done through the back end to improve security. +::: + +The `id_token` is a bearer JSON web token (JWT) signed by the JWT key-pair specified in the `config.yaml` file of the OpenDID service. +You must verify this using the JWT public key, for example, by the back end of the Web app. + +## Implicit flow + +Initiate the flow by redirecting to the **GET** `/api/v1/authorize` endpoint on the OpenDID Service and setting the following query parameters: + +- `response_type`: set value to `id_token` to indicate Implicit Flow. +- `client_id`: The client ID set in the config.yaml file. +- `redirect_uri`: OpenDID redirects to this URL after authentication. +- `scope`: set value to `openid`. +- `state`: set to a secure random number. +- `nonce`: optional value, set to a secure random number. + +**Example**: + +``` +GET /api/v1/authorize? + response_type=id_token& + client_id=example-client& + redirect_uri=http://localhost:1606/callback.html& + scope=openid& + state=o0fl4c9gwylymzw5f4ik& + nonce=ia7sa06ungxdfzaqphk2 HTTP/1.1 +``` + +After successful authentication, OpenDID redirects back to the provided `redirect_uri` with `id_token` and `state` +**fragment components**. + +**Example**: + +``` +/callback.html# + id_token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWQ6a2lsdDo0b0VkNENVV3RwbkxUVnZENVBFd2lMUmlqMWdzQmprS1JMbVpES2lCOEdqN2I2V0wiLCJ3M24iOiJjdXN0b20iLCJleHAiOjE3MTY4ODQ5MDYsImlhdCI6MTcxNjg4NDg0NiwiaXNzIjoiZGlkOmtpbHQ6NHJzQkE3dEQ1S1E4TDlXSGpGallRdUhrTWtha2NmSGRDNUNhUVVjVXh5VWpEVkhBIiwiYXVkIjoiYXBwbGljYXRpb24iLCJwcm8iOnsiRW1haWwiOiJhYmR1bEBraWx0LmlvIn0sIm5vbmNlIjoiOTFzN2ZnZDZvcjR3c2NkdGVtcXQifQ.xTy3Oyc5e-vlP10mGy0f9GqNU4LV97s77s-l7w5EwF0& + refresh_token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWQ6a2lsdDo0b0VkNENVV3RwbkxUVnZENVBFd2lMUmlqMWdzQmprS1JMbVpES2lCOEdqN2I2V0wiLCJ3M24iOiJjdXN0b20iLCJleHAiOjE3MTY4ODU0NDYsImlhdCI6MTcxNjg4NDg0NiwiaXNzIjoiZGlkOmtpbHQ6NHJzQkE3dEQ1S1E4TDlXSGpGallRdUhrTWtha2NmSGRDNUNhUVVjVXh5VWpEVkhBIiwiYXVkIjoiYXV0aGVudGljYXRpb24iLCJwcm8iOnsiRW1haWwiOiJhYmR1bEBraWx0LmlvIn0sIm5vbmNlIjoiOTFzN2ZnZDZvcjR3c2NkdGVtcXQifQ.87UHGid3OotxO8Wpfuw-1sc5fsQJVt5gc2cqp9dVHiw& + state=nitctpl7nmqcpvob7xthrw& + token_type=bearer +``` diff --git a/docs/develop/08_opendid/05_demo_project.md b/docs/develop/08_opendid/05_demo_project.md new file mode 100644 index 000000000..2eac69810 --- /dev/null +++ b/docs/develop/08_opendid/05_demo_project.md @@ -0,0 +1,30 @@ +--- +id: demo_project +title: Demo Project +--- + +The example code at [demo-project](https://github.com/KILTprotocol/opendid/tree/main/demo-project) contains a minimal application that uses OpenDID. +It's an [express](https://expressjs.com) application that exposes three things: + +- A login page that handles the dispatching of the user to the OpenDID service. +- A callback page for one of the OpenID Connect flows supported to accept the token. +- A protected resource that only authenticated users can access. + +For the demo application to work you need a running OpenDID Service and an identity wallet that follows [the Credential API spec](https://github.com/KILTprotocol/spec-ext-credential-api) (e.g. [Sporran](https://www.sporran.org/)) with a DID and Credential issued by the required attester specified in the `config.yaml` file (Default is SocialKYC). +If you follow the steps in this section in order, you have all the necessary components for the demo application to run. + +Run the pre-configured demo application with the following command: + +```bash +docker run -d -it --rm \ + --name demo-frontend \ + -p 1606:1606 \ + docker.io/kiltprotocol/opendid-demo +``` + +The demo page runs on _http://localhost:1606_. It pre-fills the Client ID value and offers login buttons to follow the implicit or authorization code flow. + +:::note +You can set the JSON web token (JWT) secret can with the `TOKEN_SECRET` environment variable inside the docker container. It must match +the one specified in the `config.yaml` file to correctly verify the `id_token`. The default is `super-secret-jwt-secret`. +::: diff --git a/docs/develop/08_opendid/06_advanced.md b/docs/develop/08_opendid/06_advanced.md new file mode 100644 index 000000000..6240b9c18 --- /dev/null +++ b/docs/develop/08_opendid/06_advanced.md @@ -0,0 +1,111 @@ +--- +id: advanced +title: Advanced Usage +--- + +## Use dynamic client management with etcd + +To dynamically create or remove OpenID Connect clients, configure the service to get its configuration from an [etcd cluster](https://etcd.io) by adding the connection parameters for the cluster in the `config.yaml` file. + +```yaml +etcd: + endpoints: ['localhost:2379'] + user: etcd-user + password: my-password + tlsDomainName: my.etcd.cluster.example.com + tlsCaCert: | + -----BEGIN CERTIFICATE----- + + -----END CERTIFICATE----- + tlsClientCert: | + -----BEGIN CERTIFICATE----- + + -----END CERTIFICATE----- + tlsClientKey: | + -----BEGIN RSA PRIVATE KEY----- + + -----END RSA PRIVATE KEY----- +``` + +All fields except `endpoints` are optional. +When everything is set up you can start adding client configurations into the etcd cluster. + +```bash +CLIENT_SPEC=$(cat <help us test this new feature!', + content: + 'DIP enables OpenID inspired cross-chain identity, help us test this new feature!', backgroundColor: '#2db528', textColor: '#fff', isCloseable: true, @@ -98,6 +99,11 @@ module.exports = { docId: 'develop/dApp/welcome', label: 'DApp Documentation', }, + { + type: 'doc', + docId: 'develop/opendid/what-is-opendid', + label: 'OpenDID Documentation', + }, ], }, { @@ -239,7 +245,10 @@ module.exports = { documents: ['README.md'], modifyContent(filename, content) { if (filename.includes('README')) { - var trimContent = content.replace("# Decentralized Identity Provider (DIP) provider pallet", "# Provider pallet") + var trimContent = content.replace( + '# Decentralized Identity Provider (DIP) provider pallet', + '# Provider pallet' + ) return { filename: '02_provider.md', content: trimContent, @@ -260,7 +269,10 @@ module.exports = { documents: ['README.md'], modifyContent(filename, content) { if (filename.includes('README')) { - var trimContent = content.replace("# Decentralized Identity Provider (DIP) consumer pallet", "# Consumer pallet") + var trimContent = content.replace( + '# Decentralized Identity Provider (DIP) consumer pallet', + '# Consumer pallet' + ) return { filename: '03_consumer.md', content: trimContent, diff --git a/markdown-link-check.config.json b/markdown-link-check.config.json index 145246f3f..a00c94747 100644 --- a/markdown-link-check.config.json +++ b/markdown-link-check.config.json @@ -16,6 +16,9 @@ "ignorePatterns": [ { "pattern": "^#.+" + }, + { + "pattern": "/(localhost)." } ] } \ No newline at end of file diff --git a/sidebars.js b/sidebars.js index 6355964f8..96e4b1ee0 100644 --- a/sidebars.js +++ b/sidebars.js @@ -25,6 +25,7 @@ module.exports = { ], workshop: [{ type: 'autogenerated', dirName: 'develop/03_workshop' }], dApp: [{ type: 'autogenerated', dirName: 'develop/07_dApp' }], + opendid: [{ type: 'autogenerated', dirName: 'develop/08_opendid'}], sdk: [ { type: 'autogenerated', dirName: 'develop/01_sdk' },