Authentication in MPC Core Kit JS SDK
There are two ways to login your users, depending on the type of authentication method you've
chosen. If you are looking for an Authentication Flow in your application (like Auth0 SPA Flow), you
can use the loginWithOAuth()
function. If you are looking to pass a JWT-based IdToken to the SDK
from your application (like Auth0 RWA Flow or even BYOA JWT provider), you can use the
loginWithJWT()
function.
As a prerequisite, before triggering the login function, you need to create a verifier for your login method on the Web3Auth Dashboard.
Creating a Verifier
Since this is a Core Kit SDK, it does not provide any default authentication methods. You need to create a custom verifier to use this SDK. This means that you need to authenticate users with your own custom authentication service. For example, while authenticating with Google, you have to use your own Google Client ID and Dashboard to authenticate users directly or use aggregate services like Auth0, Firebase, AWS Cognito etc. Additionally, you can make your own JWT token authentication system and pass over the ID Token to Web3Auth to generate a private key for them.
For enabling this, you need Create a Verifier from the Custom Auth section of the Web3Auth Developer Dashboard with your desired configuration.
If you want to know more about setting up a verifier and how to use it, please refer to the Custom Authentication Documentation.
Core Kit SDK only supports Sapphire Mainnet and Devnet networks. The other networks don't support MPC functionalities.
Log In with OAuth
loginWithOAuth(loginParams: OAuthLoginParams): Promise<void>;
Variable | Description |
---|---|
loginParams | Login Parameters |
While using the application in React Native, you can use the ux_mode
as react-native
, this helps
you to use the SDK in a React Native environment. However, the implicit login flow doesn't work in
React Native, you need to manually call the login with JWT function for it after getting the JWT
token from the auth provider.
OAuthLoginParams
- Table
- Type
Parameter | Description |
---|---|
subVerifierDetails | Details of the verifier the app needs to connect to. This is named a "sub verifier" to accommodate for aggregate verifiers as well. |
aggregateVerifierIdentifier? | Identifier/ Name of the aggregate verifier you've made on the Web3Auth Dashboard. Not needed if you're connecting to a single verifier. |
subVerifierDetailsArray? | Array of the details of verifiers the app needs to connect to. |
export type OAuthLoginParams = (SubVerifierDetailsParams | AggregateVerifierLoginParams) & { importTssKey?: string };
export interface SubVerifierDetailsParams extends BaseLoginParams {
subVerifierDetails: SubVerifierDetails;
}
export interface AggregateVerifierLoginParams extends BaseLoginParams {
aggregateVerifierIdentifier?: string;
aggregateVerifierType?: AGGREGATE_VERIFIER_TYPE;
subVerifierDetailsArray?: SubVerifierDetails[];
}
export interface BaseLoginParams {
// offset in seconds
serverTimeOffset?: number;
}
export type AGGREGATE_VERIFIER_TYPE = (typeof AGGREGATE_VERIFIER)[keyof typeof AGGREGATE_VERIFIER];
export declare const AGGREGATE_VERIFIER: {
readonly SINGLE_VERIFIER_ID: "single_id_verifier";
};
SubVerifierDetails
Contains the details of the verifier the app needs to connect to. Use this in the case of a single verifier.
- Table
- Interface
Parameter | Description |
---|---|
typeOfLogin | Type of login of this verifier, this value will affect the login flow that is adapted. For example, if you choose google , a Google sign-in flow will be used. If you choose jwt , you should be providing your own JWT token, no sign-in flow will be presented. |
verifier | Name/ Identifier of the verifier/ sub verifier in case of aggregate verifiers, you'd like your app to connect to. |
clientId | Client Id given by the auth provider. Pass a random string in case you're connecting to a JWT based setup. |
export interface SubVerifierDetails {
typeOfLogin: LOGIN_TYPE;
verifier: string;
clientId: string;
jwtParams?: Auth0ClientOptions;
hash?: string;
queryParameters?: TorusGenericObject;
customState?: TorusGenericObject;
}
export type LOGIN_TYPE = (typeof LOGIN)[keyof typeof LOGIN];
export declare const LOGIN: {
readonly GOOGLE: "google";
readonly FACEBOOK: "facebook";
readonly REDDIT: "reddit";
readonly DISCORD: "discord";
readonly TWITCH: "twitch";
readonly APPLE: "apple";
readonly GITHUB: "github";
readonly LINKEDIN: "linkedin";
readonly TWITTER: "twitter";
readonly WEIBO: "weibo";
readonly LINE: "line";
readonly EMAIL_PASSWORD: "email_password";
readonly PASSWORDLESS: "passwordless";
readonly JWT: "jwt";
readonly WEBAUTHN: "webauthn";
};
AggregateVerifierLoginParams
Contains the details of an aggregate verifier the app needs to connect to. Use this in case of an aggregate verifier.
- Table
- Interface
Parameter | Description |
---|---|
aggregateVerifierIdentifier | The name of your aggregate verifier |
subVerifierDetailsArray | An array containing the details of your sub verifiers. |
aggregateVerifierType? | What kind of aggregation is needed for your aggregate verifier? Use "single_id_verifier" by default in most cases |
export interface AggregateVerifierLoginParams extends BaseLoginParams {
aggregateVerifierIdentifier: string;
subVerifierDetailsArray: SubVerifierDetails[];
aggregateVerifierType?: AGGREGATE_VERIFIER_TYPE;
}
export type AGGREGATE_VERIFIER_TYPE = (typeof AGGREGATE_VERIFIER)[keyof typeof AGGREGATE_VERIFIER];
Usage
General Verifier
import { Web3AuthMPCCoreKit, SubVerifierDetailsParams } from "@web3auth/mpc-core-kit";
const verifierConfig = {
subVerifierDetails: {
typeOfLogin: "google",
verifier: "w3a-google-demo",
clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com",
},
} as SubVerifierDetailsParams;
await coreKitInstance.loginWithOAuth(verifierConfig);
Aggregate Verifier
import { Web3AuthMPCCoreKit, AggregateVerifierLoginParams } from "@web3auth/mpc-core-kit";
const verifierConfig = {
aggregateVerifierIdentifier: "aggregate-sapphire",
subVerifierDetailsArray: [
{
typeOfLogin: "google",
verifier: "w3a-google",
clientId: "774338308167-q463s7kpvja16l4l0kko3nb925ikds2p.apps.googleusercontent.com",
},
],
} as AggregateVerifierLoginParams;
await coreKitInstance.loginWithOAuth(verifierConfig);
Importing an existing account.
During the first-time login with Web3AuthMPCCoreKit
, you can import an existing account using the
importTssKey
parameter. To import a secp256k1 chain account, be sure to provide the private key in
hex format. For an ed25519 chain account, you need to pass the seed. By default, the ed25519 key is
formatted in base58 and is 64 bytes long. This consists of the first 32 bytes as the seed (also
known as the private key) and the last 32 bytes as the public key. Ensure that the first 32 bytes
are provided in hexadecimal format when using the ed25519 seed.
const verifierConfig = {
subVerifierDetails: {
typeOfLogin: "google",
verifier: "w3a-google-demo",
clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com",
},
importTssKey: "SECP256K1_PRIVATE_KEY_OR_ED25519_SEED",
} as SubVerifierDetailsParams;
await coreKitInstance.loginWithOAuth(verifierConfig);
Popup Mode
If you're using the popup mode (default) in your application, while logging in, you need to have a service worker running, which essentially catches the login redirect and sends it back to the main DOM with the parameters. These parameters are then used to log in the user with Web3Auth.
Service Worker
A service worker is a script that is run by the browser. It does not have any direct relationship
with the DOM and provides many out-of-the-box network-related features. Web3Auth Core Kit tKey SDK
needs a service worker relative to baseUrl
to capture the auth redirect at redirectPathName
path.
For example, while using service worker if baseUrl
is https://your-domain.com/serviceworker
then
the user will be redirected to the https://your-domain.com/serviceworker/redirect
page after
logging in where the service worker will capture the results and send it back to the original window
where login was initiated.
-
Using a service worker is required only in the popup flow.
-
A service worker is needed if you are using
popup
uxMode within your MPC Core Kit Configuration. -
For browsers where service workers are not supported, or in the case you wish to not use service workers, create and serve redirect page (i.e.
redirect.html
file).
Service Worker Setup
-
If you're using React, to set up service worker, you need to create a
sw.js
file in your public folder and register it in yourindex.html
file. You can find more information about it in this blog. -
For Angular, this guide will be helpful in setting up the service worker.
-
For Vue, this guide is a great way to get started with service workers.
Service Worker Code
You can directly copy the service worker file code from here and paste it into your respective folder. You can also find the code in our MPC Core Kit Example (Popup Flow).
Service Worker Code
/* eslint-disable */
function getScope() {
return self.registration.scope;
}
self.addEventListener("message", function (event) {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
self.addEventListener("fetch", function (event) {
try {
const url = new URL(event.request.url);
if (url.pathname.includes("redirect") && url.href.includes(getScope())) {
event.respondWith(
new Response(
new Blob(
[
`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Redirect</title>
<style>
* {
box-sizing: border-box;
}
html,
body {
background: #fcfcfc;
height: 100%;
padding: 0;
margin: 0;
}
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
h1.title {
font-size: 14px;
color: #0f1222;
font-family: "Roboto", sans-serif !important;
margin: 0;
text-align: center;
}
.spinner .beat {
background-color: #0364ff;
height: 12px;
width: 12px;
margin: 24px 2px 10px;
border-radius: 100%;
-webkit-animation: beatStretchDelay 0.7s infinite linear;
animation: beatStretchDelay 0.7s infinite linear;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
display: inline-block;
}
.spinner .beat-odd {
animation-delay: 0s;
}
.spinner .beat-even {
animation-delay: 0.35s;
}
@-webkit-keyframes beatStretchDelay {
50% {
-webkit-transform: scale(0.75);
transform: scale(0.75);
-webkit-opacity: 0.2;
opacity: 0.2;
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-opacity: 1;
opacity: 1;
}
}
@keyframes beatStretchDelay {
50% {
-webkit-transform: scale(0.75);
transform: scale(0.75);
-webkit-opacity: 0.2;
opacity: 0.2;
}
100% {
-webkit-transform: scale(1);
transform: scale(1);
-webkit-opacity: 1;
opacity: 1;
}
}
@media (min-width: 768px) {
h1.title {
font-size: 14px;
}
p.info {
font-size: 28px;
}
.spinner .beat {
height: 12px;
width: 12px;
}
}
</style>
</head>
<body>
<div id="message" class="container">
<div class="spinner content" id="spinner">
<div class="beat beat-odd"></div>
<div class="beat beat-even"></div>
<div class="beat beat-odd"></div>
</div>
<h1 class="title content" id="closeText" style="display: none;">You can close this window now</h1>
</div>
<script
src="https://cdn.jsdelivr.net/npm/@toruslabs/broadcast-channel@10.0.2/dist/broadcastChannel.umd.min.js"
integrity="sha256-q78HZzZl8u46uVK0a+t5vzPyAevMwEWHi43ok+P7/O8="
crossorigin="anonymous"
></script>
<script>
function storageAvailable(type) {
var storage;
try {
storage = window[type];
var x = "__storage_test__";
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return (
e &&
// everything except Firefox
(e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === "QuotaExceededError" ||
// Firefox
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
// acknowledge QuotaExceededError only if there's something already stored
storage &&
storage.length !== 0
);
}
}
function showCloseText() {
var closeText = document.getElementById("closeText");
var spinner = document.getElementById("spinner");
if (closeText) {
closeText.style.display = "block";
}
if (spinner) {
spinner.style.display = "none";
}
}
var isLocalStorageAvailable = storageAvailable("localStorage");
// set theme
let theme = "light";
if (isLocalStorageAvailable) {
var torusTheme = localStorage.getItem("torus-theme");
if (torusTheme) {
theme = torusTheme.split("-")[0];
}
}
if (theme === "dark") {
document.querySelector("body").style.backgroundColor = "#24252A";
}
var bc;
var broadcastChannelOptions = {
// type: 'localstorage', // (optional) enforce a type, oneOf['native', 'idb', 'localstorage', 'node'
webWorkerSupport: false, // (optional) set this to false if you know that your channel will never be used in a WebWorker (increase performance)
};
var instanceParams = {};
var preopenInstanceId = new URL(window.location.href).searchParams.get("preopenInstanceId");
if (!preopenInstanceId) {
document.getElementById("message").style.visibility = "visible";
// in general oauth redirect
try {
var url = new URL(location.href);
var hash = url.hash.substr(1);
var hashParams = {};
if (hash) {
hashParams = hash.split("&").reduce(function (result, item) {
var parts = item.split("=");
result[parts[0]] = parts[1];
return result;
}, {});
}
var queryParams = {};
for (var key of url.searchParams.keys()) {
queryParams[key] = url.searchParams.get(key);
}
var error = "";
try {
if (Object.keys(hashParams).length > 0 && hashParams.state) {
instanceParams = JSON.parse(base64urlLib.decode(decodeURIComponent(decodeURIComponent(hashParams.state)))) || {};
if (hashParams.error) error = hashParams.error;
} else if (Object.keys(queryParams).length > 0 && queryParams.state) {
instanceParams = JSON.parse(base64urlLib.decode(decodeURIComponent(decodeURIComponent(queryParams.state)))) || {};
if (queryParams.error) error = queryParams.error;
}
} catch (e) {
console.error(e);
}
if (instanceParams.redirectToOpener) {
// communicate to window.opener
window.opener.postMessage(
{
channel: "redirect_channel_" + instanceParams.instanceId,
data: {
instanceParams: instanceParams,
hashParams: hashParams,
queryParams: queryParams,
},
error: error,
},
"http://localhost:3000"
);
} else {
// communicate via broadcast channel
bc = new broadcastChannelLib.BroadcastChannel("redirect_channel_" + instanceParams.instanceId, broadcastChannelOptions);
bc.postMessage({
data: {
instanceParams: instanceParams,
hashParams: hashParams,
queryParams: queryParams,
},
error: error,
}).then(function () {
bc.close();
console.log("posted", {
queryParams,
instanceParams,
hashParams,
});
setTimeout(function () {
window.close();
showCloseText();
}, 5000);
});
}
} catch (err) {
console.error(err, "service worker error in redirect");
bc && bc.close();
window.close();
showCloseText();
}
} else {
// in preopen, awaiting redirect
try {
bc = new broadcastChannelLib.BroadcastChannel("preopen_channel_" + preopenInstanceId, broadcastChannelOptions);
bc.onmessage = function (ev) {
var { preopenInstanceId: oldId, payload, message } = ev.data;
if (oldId === preopenInstanceId && payload && payload.url) {
window.location.href = payload.url;
} else if (oldId === preopenInstanceId && message === "setup_complete") {
bc.postMessage({
data: {
preopenInstanceId: preopenInstanceId,
message: "popup_loaded",
},
});
}
if (ev.error && ev.error !== "") {
console.error(ev.error);
bc.close();
}
};
} catch (err) {
console.error(err, "service worker error in preopen");
bc && bc.close();
window.close();
showCloseText();
}
}
</script>
</body>
</html>
${""}
`,
],
{ type: "text/html" },
),
),
);
}
} catch (error) {
console.error(error);
}
});
Redirect Mode
If you are using the redirect mode, don't have to use the service worker or redirect.html
file.
You can get login results by calling the init()
function on the redirected page mount.
For example, if baseUrl
is https://your-domain.com
and redirectPathName
is auth
then the
user will be redirected to the https://your-domain.com/auth
page after logging in where you can
get login result by calling init()
function on redirected page mount.
Log In with JWT (BYOA)
loginWithJWT(loginParams: JWTLoginParams): Promise<void>
Variable | Description |
---|---|
loginParams | Login Parameters |
JWTLoginParams
- Table
- Type
Parameter | Description |
---|---|
verifier | Name of the verifier created on Web3Auth Dashboard. In the case of Aggregate Verifier, the name of the top-level aggregate verifier. |
verifierId | Unique Identifier for the User. The verifier identifier field is set for the verifier/sub verifier. E.g. "sub" field in your JWT ID Token. |
idToken | The idToken received from the Auth Provider. |
subVerifier? | Name of the sub verifier in case of aggregate verifier setup. This field is mandatory in the case of an aggregate verifier. |
extraVerifierParams? | Extra verifier params in case of a WebAuthn verifier type. |
additionalParams? | Any additional parameter (key-value pair) you'd like to pass to the login function. |
importTssKey? | Key to import key into TSS during first time login. For secp256k1 curve, you need to pass the private key, and for ed25519 curve you need to pass the seed. The ed25519 seed is hashed to generate 64 bytes, where first 32 bytes are used to generate the public key, and last 32 bytes are used as private key. |
prefetchTssPublicKeys? | Number of TSS public keys to prefetch. |
interface JWTLoginParams {
/**
* Name of the verifier created on Web3Auth Dashboard. In case of Aggregate Verifier, the name of the top level aggregate verifier.
*/
verifier: string;
/**
* Unique Identifier for the User. The verifier identifier field set for the verifier/ sub verifier. E.g. "sub" field in your on jwt id token.
*/
verifierId: string;
/**
* The idToken received from the Auth Provider.
*/
idToken: string;
/**
* Name of the sub verifier in case of aggregate verifier setup. This field should only be provided in case of an aggregate verifier.
*/
subVerifier?: string;
/**
* Extra verifier params in case of a WebAuthn verifier type.
*/
extraVerifierParams?: PasskeyExtraParams;
/**
* Any additional parameter (key value pair) you'd like to pass to the login function.
*/
additionalParams?: ExtraParams;
/**
* Key to import key into Tss during first time login.
*/
importTssKey?: string;
/**
* Number of TSS public keys to prefetch. For the best performance, set it to
* the number of factors you want to create. Set it to 0 for an existing user.
* Default is 1, maximum is 3.
*/
prefetchTssPublicKeys?: number;
}
export interface ExtraParams {
[key: string]: unknown;
}
export type WebAuthnExtraParams = {
signature?: string;
clientDataJSON?: string;
authenticatorData?: string;
publicKey?: string;
challenge?: string;
rpOrigin?: string;
credId?: string;
transports?: AuthenticatorTransport[];
};
type AuthenticatorTransport = "ble" | "hybrid" | "internal" | "nfc" | "usb";
Usage
General Verifier
import { Web3AuthMPCCoreKit, JWTLoginParams } from "@web3auth/mpc-core-kit";
const loginRes = await signInWithGoogle();
const idToken = await loginRes.user.getIdToken(true);
const parsedToken = parseToken(idToken);
const jwtLoginParams = {
verifier: "w3a-firebase-demo",
verifierId: parsedToken.email,
idToken,
} as JWTLoginParams;
await coreKitInstance.loginWithJWT(jwtLoginParams);
Aggregate Verifier
import { Web3AuthMPCCoreKit, JWTLoginParams } from "@web3auth/mpc-core-kit";
const loginRes = await signInWithGoogle();
const idToken = await loginRes.user.getIdToken(true);
const parsedToken = parseToken(idToken);
const jwtLoginParams = {
verifier: "aggregate-sapphire"
subVerifier: "w3a-google",
verifierId: parsedToken.email,
idToken,
} as JWTLoginParams;
await coreKitInstance.loginWithJWT(jwtLoginParams);
Importing an existing account.
During the first-time login with Web3AuthMPCCoreKit
, you can import an existing account using the
importTssKey
parameter. To import a secp256k1 chain account, be sure to provide the private key in
hex format. For an ed25519 chain account, you need to pass the seed. By default, the ed25519 key is
formatted in base58 and is 64 bytes long. This consists of the first 32 bytes as the seed (also
known as the private key) and the last 32 bytes as the public key. Ensure that the first 32 bytes
are provided in hexadecimal format when using the ed25519 seed.
import { Web3AuthMPCCoreKit, JWTLoginParams } from "@web3auth/mpc-core-kit";
const loginRes = await signInWithGoogle();
const idToken = await loginRes.user.getIdToken(true);
const parsedToken = parseToken(idToken);
const jwtLoginParams = {
verifier: "aggregate-sapphire"
subVerifier: "w3a-google",
verifierId: parsedToken.email,
idToken,
importTssKey: "SECP256K1_PRIVATE_KEY_OR_ED25519_SEED"
} as JWTLoginParams;
await coreKitInstance.loginWithJWT(jwtLoginParams);
Logging out the User
logout(): Promise<void>;
Disconnect the user's connected wallet/ provider and log them out of the Web3Auth MPC Core Kit SDK.
Usage
await coreKitInstance.logout();
Backend verification
To verify the user in the backend, you can retrieve the user's signature from frontend, and validate
it using the SignatureValidator
from the
@toruslabs/signature-validator
package in the backend.
Retrieve user's signature
To retrieve user's signature you can use the signatures
getter from Web3AuthMPCCoreKit
.
const signatures = await coreKitInstance.signatures;
// Send these signatures to backend through an API
Verify the signatures in backend
For verification you'll need to install couple of packages,
@toruslabs/signature-validator and
@toruslabs/fnd-base, and use
SignatureValidator
to validate the signatures.
const { fetchLocalConfig } = require("@toruslabs/fnd-base");
const { SignatureValidator } = require("@toruslabs/signature-validator");
// Here network can be "sapphire_mainnet" or "sapphire_testnet", since MPC doesn't support
// legacy networks.
const network = "sapphire_mainnet";
// Fetch the node details
const nodeDetails = fetchLocalConfig(network, "secp256k1");
const nodePubX = [];
const nodePubY = [];
nodeDetails.torusNodePub.forEach((key) => {
nodePubX.push(key.X);
nodePubY.push(key.Y);
});
// Create the SignatureValidator object
const sigValidator = new SignatureValidator({
nodePubKeyX: nodePubX.join(","),
nodePubKeyY: nodePubY.join(","),
});
// Get the signatures from the frontend & validate the signatures
const result = sigValidator.authenticate(signatures, { skipExpValidation: false });
if (!result) {
// Handle invalid singature
}
// Handle the valid signature