Skip to main content

Integrating with II

Intermediate
Tutorial

Overview

This guide shows an example of how to integrate Internet Identity into an application by using a simple 'Who am I?' backend canister and a frontend UI that returns the Internet Identity principal of the user who calls the backend's whoami method.

This project uses the pullable version of the Internet identity canister. A pullable canister is a canister that provides a public service at a static canister ID. To learn more about pullable canisters, please see the documentation.

Prerequisites

Before you start, verify that you have:

Step 1: To get started, open a terminal window and create a new project:

Start dfx:

dfx start --clean --background

Use dfx new <project_name> to create a new project:

dfx new ii_integration

You will be prompted to select the language that your backend canister will use. Select 'Motoko':

? Select a backend language: ›
❯ Motoko
  Rust
  TypeScript (Azle)
  Python (Kybra)

Then, select a frontend framework for your frontend canister. Select 'Vanilla JS':

  ? Select a frontend framework: ›
  SvelteKit
  React
  Vue
❯ Vanilla JS
  No JS template
  No frontend canister

Lastly, you can include extra features to be added to your project. Select 'Internet Identity'.

  ? Add extra features (space to select, enter to confirm) ›
x Internet Identity
⬚ Bitcoin (Regtest)
⬚ Frontend tests

Step 2: For this project, you'll use a simple 'Who am I?' function for the backend canister. Open the src/ii_integration_backend/main.mo file and replace the existing content with the following:

actor {
    public shared (msg) func whoami() : async Principal {
        msg.caller
    };
};

In this actor, there is a single method that responds with the caller's principal. This will show if you make a request from the application's frontend using an authenticated Internet Identity or an AnonymousIdentity.

Step 3: Install the @dfinity/auth-client package:

npm install @dfinity/auth-client

Step 4: Insert the following code into the src/ii_integration_frontend/src/index.js file:

/* A simple webapp that authenticates the user with Internet Identity and that
 * then calls the whoami canister to check the user's principal.
 */

import { Actor, HttpAgent } from "@dfinity/agent";
import { AuthClient } from "@dfinity/auth-client";

const webapp_id = process.env.WHOAMI_CANISTER_ID;

// The interface of the whoami canister
const webapp_idl = ({ IDL }) => {
  return IDL.Service({ whoami: IDL.Func([], [IDL.Principal], ["query"]) });
};
export const init = ({ IDL }) => {
  return [];
};

// Autofills the <input> for the II Url to point to the correct canister.
document.body.onload = () => {
  let iiUrl;
  if (process.env.DFX_NETWORK === "local") {
    iiUrl = `http://localhost:4943/?canisterId=${process.env.II_CANISTER_ID}`;
  } else if (process.env.DFX_NETWORK === "ic") {
    iiUrl = `https://${process.env.II_CANISTER_ID}.ic0.app`;
  } else {
    iiUrl = `https://${process.env.II_CANISTER_ID}.dfinity.network`;
  }
  document.getElementById("iiUrl").value = iiUrl;
};

document.getElementById("loginBtn").addEventListener("click", async () => {
  // When the user clicks, we start the login process.
  // First we have to create and AuthClient.
  const authClient = await AuthClient.create();

  // Find out which URL should be used for login.
  const iiUrl = document.getElementById("iiUrl").value;

  // Call authClient.login(...) to login with Internet Identity. This will open a new tab
  // with the login prompt. The code has to wait for the login process to complete.
  // We can either use the callback functions directly or wrap them in a promise.
  await new Promise((resolve, reject) => {
    authClient.login({
      identityProvider: iiUrl,
      onSuccess: resolve,
      onError: reject,
    });
  });

  // At this point we're authenticated, and we can get the identity from the auth client:
  const identity = authClient.getIdentity();
  // Using the identity obtained from the auth client, we can create an agent to interact with the IC.
  const agent = new HttpAgent({ identity });
  // Using the interface description of our webapp, we create an actor that we use to call the service methods.
  const webapp = Actor.createActor(webapp_idl, {
    agent,
    canisterId: webapp_id,
  });
  // Call whoami which returns the principal (user id) of the current user.
  const principal = await webapp.whoami();
  // show the principal on the page
  document.getElementById("loginStatus").innerText = principal.toText();
});

Depending on your web browser, you may need to change the http://rdmx6-jaaaa-aaaaa-aaadq-cai.localhost:4943 value:

  • Chrome, Firefox: http://<canister_id>.localhost:4943

  • Safari: http://localhost:4943?canisterId=<canister_id>

This code does the following:

  • Interacts with the backend actor to call the whoami method.
  • Creates an AuthClient using the auth-client library that is used to authenticate with Internet Identity.
  • Retrieves the identity from the AuthClient.
  • Uses the identity to create an agent that interacts with ICP.
  • Then, uses the interface description of the app to create an actor that's used to call the app's service methods.

If you used a project name other than ii_integration, you will need to rename the imports and environment variables in the code.

Step 5: Insert the following code into the src/ii_integration_frontend/src/index.html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>greet</title>
    <base href="/" />
    <link rel="icon" href="favicon.ico" />
    <link type="text/css" rel="stylesheet" href="main.css" />
  </head>
  <body>
    <main>
      <img src="logo2.svg" alt="DFINITY logo" />
      <br />
      <br />
      <form>
        <button id="login">Login!</button>
      </form>
      <br />
      <form>
        <button id="whoAmI">Who Am I</button>
      </form>
      <section id="principal"></section>
    </main>
  </body>
</html>

This code provides a simple UI for us to interact with our application.

View the repo for this code if you would rather clone the example.

Step 6: Deploy the project:

dfx deps deploy
dfx deploy

Step 8: Navigate to the frontend canister's URL in your web browser. You will see the frontend of the app:

Local integration

Step 7: Then, select 'Log in'.

You'll be redirected to the II frontend. Since you're running this locally, you will be using a local, non-production Internet Identity. To create one, follow the on-screen steps.

Step 8: Create a local Internet Identity

  • Select 'Create New' from the UI.

Internet Identity 1

  • Next, select 'Create Passkey'.

Internet Identity 2

  • When prompted, choose how to create your passkey, either on your current device or on another device.

Internet Identity 3

  • Then, enter the CAPTCHA to continue.

Internet Identity 4

Your Internet Identity has been created! It'll be shown on the screen, and it is recommended that you write it down in a safe location to save it.

This number is your Internet Identity. With this number and your passkey, you will be able to create and securely connect to Internet Computer dapps. If you lose this number, you will lose any accounts that were created with it. This number is not secret, but is unique to you.

Once you save it, select the 'I saved it, continue' button.

Step 9: Once you are redirected back to the frontend of the app, click the 'Click me!' button.

Step 10: Your Internet Identity's principal ID will be returned:

Local integration 4

Local frontend development

When modifying this example's frontend, it is recommended to develop using a local development server instead of using the deployed frontend canister. This is because using a local development server will enable Hot Module Reloading, allowing you to see any modifications made to your frontend instantaneously, rather than having to redeploy the frontend canister to see the changes.

To start a local development server, run npm run start. The output will contain the local address the project is running at, such as 127.0.0.1:4943.

End-to-end testing

To run end-to-end testing for Internet Identity integrations, you can use the Internet Identity Playwright plugin.

To use this plugin, first install Playwright, then install the plugin itself with a package manager:

# Install with npm
npm install --save-dev @dfinity/internet-identity-playwright

# Install with pnpm
pnpm add --save-dev @dfinity/internet-identity-playwright

# Install with yarn
yarn add -D @dfinity/internet-identity-playwright

Import the plugin into your Playwright test file:

import {testWithII} from '@dfinity/internet-identity-playwright';

Then begin writing your tests, such as:

testWithII('should sign-in with a new user', async ({page, iiPage}) => {
  await page.goto('/');

  await iiPage.signInWithNewIdentity();
});

testWithII('should sign-in with an existing new user', async ({page, iiPage}) => {
  await page.goto('/');

  await iiPage.signInWithIdentity({identity: 10003});
});

In this test, iiPage represents your application's page that initiates the authentication flow with Internet Identity. By default, the test will look for a button identified by [data-tid=login-button]. This can be customized by configuring your own selector:

const loginSelector = '#login';

testWithII('should sign-in with a new user', async ({page, iiPage}) => {
  await page.goto('/');

  await iiPage.signInWithNewIdentity({selector: loginSelector});
});

testWithII('should sign-in with an existing new user', async ({page, iiPage}) => {
  await page.goto('/');

  await iiPage.signInWithIdentity({identity: 10003, selector: loginSelector});
});

If desired, you can have the test wait for Internet Identity to be ready by providing the local replica URL and the canister ID of your local Internet Identity instance:

testWithII.beforeEach(async ({iiPage, browser}) => {
  const url = 'http://127.0.0.1:4943';
  const canisterId = 'rdmx6-jaaaa-aaaaa-aaadq-cai';

  await iiPage.waitReady({url, canisterId});
});

You can also configure a timeout parameter that indicates how long the function should wait for Internet Identity before failing:

testWithII.beforeEach(async ({iiPage, browser}) => {
  const url = 'http://127.0.0.1:4943';
  const canisterId = 'rdmx6-jaaaa-aaaaa-aaadq-cai';
  const timeout = 30000;

  await iiPage.waitReady({url, canisterId, timeout});
});

Once your tests are ready, run them with the command:

npx playwright test

View more details in the plugin's repo.

Resources