Skip to main content

NFID

Intermediate
Tutorial

Overview

NFID is a form of digital identity built on ICP. Similar to Internet Identity, NFID allows users to sign into services without downloading an app or extension and can be used across several different dapps.

NFID is different from Internet Identity, however, in the fact that is allows you to sign in to a dapp using your email or Google account. With Internet Identity, these log in options aren't available. This makes NFID a great option for those that want to use email or Google authentication methods.

NFID also supports signing in with a passkey stored on a mobile device or local keychain, which is the same method used by Internet Identity.

Using NFID

Prerequisites

Before starting the guide, verify the following:

  • You have Node.js 16.0.0 or newer installed for frontend development and can install packages using npm install in your project. For information about installing node for your local operating system and package manager, see the Node website.

  • You have downloaded and installed the IC SDK package as described in the download and install page.

Check out the GitHub repo for this project to clone and deploy the project quickly.

Create a new project

First, create a new project with the dfx new command:

dfx new NFID_project

If using dfx v0.17.0 and newer, select 'Motoko' and 'Vanilla JS' when prompted.

Edit the backend canister

Replace the default Motoko backend canister code (src/NFID_backend/main.mo) with the following:

src/NFID_backend/main.mo
import Principal "mo:base/Principal";

actor {
public shared query({caller}) func greet(name : Text) : async Text {
return "Hello, " # name # "! " # "Your PrincipalId is: " # Principal.toText(caller);
};
};

Update the project's dfx.json file.

Replace the dfx.json file's content with the following configuration:

dfx.json
{
"canisters": {
"NFID_backend": {
"main": "src/NFID_backend/main.mo",
"type": "motoko"
},
"NFID_frontend": {
"dependencies": [
"NFID_backend"
],
"frontend": {
"entrypoint": "src/NFID_frontend/src/index.html"
},
"source": [
"src/NFID_frontend/assets"
],
"type": "assets"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"version": 1
}

Install the auth-client package.

Install the DFINITY auth-client package with the command:

npm install --save @dfinity/auth-client

Create a file called index.html in the subdirectory src/NFID_frontend/src/.

Insert the following code into the index.html file:

src/NFID_frontend/src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>NFID example</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 />
<div>
<button id="login">Log me in</button>
</div>
<div id="principalId"></div>
<form action="#">
<label for="name">Enter your name: &nbsp;</label>
<input id="name" alt="Name" type="text" />
<button type="submit">Click Me!</button>
</form>
<section id="greeting"></section>
</main>
</body>
</html>

Create an index.js file in the src/NFID_frontend/src subdirectory.

Insert the following code into this new index.js file:

src/NFID_frontend/src/index.js
import { Actor } from "@dfinity/agent";
import { AuthClient } from "@dfinity/auth-client";
import { NFID_backend } from "../../declarations/NFID_backend";

let authClient = null;

async function init() {
authClient = await AuthClient.create();
}

document.querySelector("form").addEventListener("submit", async (e) => {
e.preventDefault();
const button = e.target.querySelector("button");

const name = document.getElementById("name").value.toString();

button.setAttribute("disabled", true);

// Interact with foo actor, calling the greet method
const greeting = await NFID_backend.greet(name);

button.removeAttribute("disabled");

document.getElementById("greeting").innerText = greeting;

return false;
});

function handleSuccess() {
const principalId = authClient.getIdentity().getPrincipal().toText();

document.getElementById(
"principalId"
).innerText = `Your PrincipalId: ${principalId}`;
}

document.getElementById("login").addEventListener("click", async (e) => {
if (!authClient) throw new Error("AuthClient not initialized");

authClient.login({
onSuccess: handleSuccess,
});
});

init();

Deploy your project

Deploy the project with the command:

dfx deploy

You will receive the URL for the frontend and backend canisters running locally:

Deployed canisters.
URLs:
Frontend canister via browser
NFID_frontend: http://127.0.0.1:4943/?canisterId=br5f7-7uaaa-aaaaa-qaaca-cai
Backend canister via Candid interface:
NFID_backend: http://127.0.0.1:4943/?canisterId=bw4dl-smaaa-aaaaa-qaacq-cai&id=be2us-64aaa-aaaaa-qaabq-cai

Open the NFID_frontend URL in your web browser. You'll see the following UI:

UI

Configure NFID

Currently, the Log me in button doesn't have any functionality when clicked. Let's configure it to use NFID.

Replace the existing index.js code with the following:

src/NFID_frontend/src/index.js
import { Actor } from "@dfinity/agent";
import { AuthClient } from "@dfinity/auth-client";
import { NFID_backend } from "../../declarations/NFID_backend";

let authClient = null;

async function init() {
authClient = await AuthClient.create();
}

document.querySelector("form").addEventListener("submit", async (e) => {
e.preventDefault();
const button = e.target.querySelector("button");

const name = document.getElementById("name").value.toString();

button.setAttribute("disabled", true);

// Interact with foo actor, calling the greet method
const greeting = await NFID_backend.greet(name);

button.removeAttribute("disabled");

document.getElementById("greeting").innerText = greeting;

return false;
});

function handleSuccess() {
const principalId = authClient.getIdentity().getPrincipal().toText();

document.getElementById(
"principalId"
).innerText = `Your PrincipalId: ${principalId}`;

Actor.agentOf(NFID_backend).replaceIdentity(
authClient.getIdentity()
);
}

document.getElementById("login").addEventListener("click", async (e) => {
if (!authClient) throw new Error("AuthClient not initialized");

const APP_NAME = "NFID example";
const APP_LOGO = "https://nfid.one/icons/favicon-96x96.png";
const CONFIG_QUERY = `?applicationName=${APP_NAME}&applicationLogo=${APP_LOGO}`;

const identityProvider = `https://nfid.one/authenticate${CONFIG_QUERY}`;

authClient.login({
identityProvider,
onSuccess: handleSuccess,
windowOpenerFeatures: `
left=${window.screen.width / 2 - 525 / 2},
top=${window.screen.height / 2 - 705 / 2},
toolbar=0,location=0,menubar=0,width=525,height=705
`,
});
});

init();

Redeploy the canisters

To implement the changes, redeploy the canisters with dfx deploy.

Now, when you click 'Log me in', you'll get an NFID login prompt:

NFID login

Resources