Skip to main content

Integrating Internet Identity

Intermediate
Tutorial

This guide demonstrates an example of how to integrate Internet Identity as an application authentication service. In this example, a user can interact with the application's website to log in with their identity and then call a backend canister's function whose behavior depends on the identity of the caller.

For simplicity, we use the whoami function that simply returns the caller's principal. (While real-world use cases for Internet Identity are out of scope of this guide, note that the most common use case is authentication, for example, letting the backend process certain requests only from an authorized caller while rejecting calls from everyone else.)

Step 1: Create or open a project.

First, use dfx start to start a local development environment (if necessary) and create a new project or open an existing project.

Alternatively, you can open this project in ICP Ninja, a web-based IDE for temporary project deployments, or clone the "Who am I?" sample from GitHub.

Creating a new project

Create a new project with the command:

dfx new internet_identity_app --type=motoko --frontend react --extras internet-identity
cd internet_identity_app

To use a Rust backend canister, use --type=rust instead.

The --extras flag will add the pullable version of the Internet Identity canister to your project. A pullable canister is a canister that provides a public service at a static canister ID. Learn more about pullable canisters.

Using an existing project

If you already have a project that was not created with the extra Internet Identity feature, you can modify the project to use the pullable Internet Identity canister. First, add the canister configuration to the project's dfx.json file:

motoko/who_am_i/dfx.json
loading...

Step 2: Create a "Who am I" function.

To add a simple "Who am I?" function, insert the following code into the backend canister's source code file:

motoko/who_am_i/internet_identity_app_backend/app.mo
loading...

Step 3: Edit frontend code.

In the frontend canister code, add a method to the App class to call the backend function and initialize the auth client:

motoko/who_am_i/internet_identity_app_frontend/src/App.jsx
loading...

Make sure to always create a single AuthClient instance on page load and reuse it within your click handlers.

If you are developing using the Safari web browser, you need to change the value returned for the local development environment to http://localhost:4943?canisterId=<canister_id>.

Install the required npm package:

npm install @dfinity/auth-client

Make sure that all @dfinity/<package> dependencies in package.json are the same version.

Step 4: Deploy the application:

dfx deploy

Open the frontend URL that is returned in the deployment output. Your dapp's frontend will be displayed.

Click the "Login" button. You'll be redirected to the Internet Identity 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.

Click the "Who am I" button and it should return your Internet Identity principal:

Your principal is: 5uylz-j7fcd-isj73-gp57f-xwwyy-po2ib-7iboa-fdkdv-nrsam-3bd3r-qqe

The above principal is an example. The principal returned will be different based on the account and the environment where the application and Internet Identity are running.

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:

e2e/login.spec.ts
import {testWithII} from '@dfinity/internet-identity-playwright';

Then begin writing your tests, such as:

e2e/login.spec.ts
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:

e2e/login.spec.ts
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 development environment URL and the canister ID of your local Internet Identity instance:

e2e/login.spec.ts
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:

e2e/login.spec.ts
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