Skip to main content

Enabling Implicit Authentication Flow in tKey

If you're looking for using an implicit authentication flow in your application, you can use the Torus Service Provider SDK to do so. The Torus Service Provider is built on top of the CustomAuth SDK from Web3Auth, which is an internal library used in the PnP SDKs to provide a common interface for authentication. Here, you need to do some additional setup to enable the SDK to be able to catch the authentication redirect.

This guide will help you in setting up the Torus Service Provider SDK in your application. It is a 3 step process in general:

  • Instantiating the Torus Service Provider
  • Initiating the Service Provider Instance within tKey
  • Handling the OAuth Redirection
  • Triggering Login

Instantiation

Service Provider in tKey is used for generating a Share A, i.e. the private key share managed by a wallet service provider via their authentication flows. This share in our wallet infrastructure refers to the social login aspect, where we associate a private key share with the user's social login, enabling the seamless login experience.

@tkey/service-provider-torus

npm install --save @tkey/service-provider-torus

Parameters

class TorusServiceProvider extends ServiceProviderBase {
customAuthInstance: CustomAuth;
singleLoginKey: BN;
torusKey: TorusKey;
migratableKey: BN | null;
customAuthArgs: CustomAuthArgs;
constructor({ enableLogging, postboxKey, customAuthArgs }: TorusServiceProviderArgs);
static fromJSON(value: StringifiedType): TorusServiceProvider;
init(params: InitParams): Promise<void>;
triggerLogin(params: SubVerifierDetails): Promise<TorusLoginResponse | null>;
triggerAggregateLogin(params: AggregateLoginParams): Promise<TorusAggregateLoginResponse>;
triggerHybridAggregateLogin(
params: HybridAggregateLoginParams,
): Promise<TorusHybridAggregateLoginResponse>;
toJSON(): StringifiedType;
}

This TorusServiceProvider constructor takes an object with customAuthArgs as input. It contains the following parameters:

customAuthArgs

The customAuthArgs object is the mandatory object needed to be passed within the TorusServiceProvider constructor. It contains the following parameters:

ParameterTypeDescriptionDefault ValueMandatory
baseUrlstringRedirect Uri for OAuth is baseUrl+redirectPathName which means that you must specify baseUrl+redirectPathName as redirect_uri at verifier's interface.Yes
metadataUrl?stringSpecify a custom metadata host.https://metadata.tor.usNo
networkTORUS_NETWORK_TYPEAuth Network to target options: mainnet, testnet, aqua & cyan.mainnetNo
networkUrlstringNetwork Url to read blockchain data from (eg: infura url)No
enableLogging?booleanThis option is used to specify whether to enable logging.falseNo
enableOneKey?booleanUse one key feature that allows users to have the same account in tKey. Note: This flag shouldn't be changed once set for an account; changing it will lead to a different account.falseNo
redirectToOpener?booleanFor chrome extensions, the general methods for capturing auth redirects don't work. So, we redirect to the window which opens the auth window.falseNo
redirectPathName?stringThis option is used to specify the url path where user will be redirected after login. Redirect Uri for OAuth is baseUrl/redirectPathName. At verifier's interface, please use baseUrl/redirectPathName as the redirect_uri.redirectNo
apiKey?stringAPI Key for Web3Auth to enable higher access limitsNo
uxMode?UX_MODE_TYPETwo uxModes are supported:
  • 'popup': In this uxMode, a popup will be shown to user for login.
  • 'redirect': In this uxMode, user will be redirected to a new window tab for login.
Use of 'REDIRECT' mode is recommended in browsers where popups might get blocked.
popupNo
locationReplaceOnRedirect?booleanWhether to replace the url hash/query params from OAuth at the end of the redirect flowfalseNo
popupFeatures?stringFeatures of popup window. Please check https://developer.mozilla.org/en-US/docs/Web/API/Window/open#window_features for further documentation.No
storageServerUrl?stringSpecify a custom storage server urlhttps://broadcast-server.tor.usNo

Usage

const web3AuthClientId = "YOUR_WEB3AUTH_CLIENT_ID"; // get from https://dashboard.web3auth.io

// Configuration of Service Provider
const customAuthArgs = {
web3AuthClientId,
baseUrl: `${window.location.origin}/serviceworker`,
network: "sapphire_mainnet", // based on the verifier network.
uxMode: "popup", // or redirect
};

const serviceProvider = new TorusServiceProvider({
enableLogging: false,
customAuthArgs: customAuthArgs as any,
});

Initializing Service Provider

You need to initialize your Service Provider within your constructor function to use it while logging your user in through the social accounts. This is done by calling the init() function within the tKey instance's serviceProvider property.

tKey.serviceProvider.init(initParams);

Parameters

ParameterTypeDescriptionDefault Value
skipSw?booleanSkips the installation / check for service workerfalse
skipInit?booleanSkips the init functionfalse
skipPrefetch?booleanSkips the prefetching of redirect urlfalse

Usage

useEffect(() => {
const init = async () => {
// Initialization of Service Provider
try {
await (tKey.serviceProvider as any).init();
} catch (error) {
console.error(error);
}
};
init();
}, []);

Handling Redirection

In the Implicit Flow, you need to set up a redirect page to capture the authentication redirect. This is needed since the authentication redirect contains the authentication information of the user, which is needed to generate the OAuthKey. The redirect page is used to capture this information and pass it over to the Torus Service Provider.

The uxMode parameter in the customAuthArgs object is used to determine the type of redirect page to be used. There are two types of redirect

  • Popup Mode (default): We deploy a service worker to capture the redirect.
  • Redirect Mode: You need to create a redirect page to capture the redirect.

Redirect Page

You can get login result by calling getRedirectResult on redirected page mount. For example, if baseUrl is http://localhost:3000 and redirectPathName is auth then user will be redirected to http://localhost:3000/auth page after login where you can get login result by calling getRedirectResult on redirected page mount.

Usage

useEffect(() => {
const init = async () => {
// Initialization of Service Provider
try {
// Init is required for Redirect Flow but skip fetching sw.js and redirect.html )
(tKey.serviceProvider as any).init({ skipInit: true });
if (window.location.pathname === "/auth" && window.location.hash.includes("#state")) {
let result = await (tKey.serviceProvider as any).directWeb.getRedirectResult();
tKey.serviceProvider.postboxKey = new BN((result.result as any).privateKey!, "hex");
await tKey.initialize();
}
} catch (error) {
console.error(error);
}
};
init();
}, []);

Service Worker

A service worker is a scripts 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 http://localhost:3000/serviceworker then user will be redirected to http://localhost:3000/serviceworker/redirect page after login where service worker will capture the results and send it back to original window where login was initiated.

tip
  • Using service worker is optional, but highly recommended. You can skip it by passing skipSw param while initializing tKey.

  • Service worker is needed if you are using popup uxMode within your Service Provider 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 setup service worker, you need to create a sw.js file in your public folder and register it in your index.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 in your respective folder. You can also find the code in our tKey JS Examples (Popup Flow).

Service Worker Code
/public/serviceworker/sw.js
/* 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);
}
});

Log In

The login with the tKey SDK is a two step process. First, you need to trigger the login process by calling the triggerLogin() function of the Service Provider. Following which using the returned information, use the initialize() function of the tKey to generate the Threshold Key corresponding to the user.

However, before starting this process, you need to set up Custom Authentication on your Web3Auth Dashboard. For this, you need to Create a Verifier from the Custom Auth section of the Web3Auth Developer Dashboard with your desired configuration.

tip

If you want to know more about setting up a verifier and how to use it, please refer to the Custom Authentication Documentation.

Triggering Login

tKey.serviceProvider.triggerLogin(SubVerifierDetails)

This is a needed step since this will generate a private key which will be needed by the tKey to generate it's share. This is done by calling the triggerLogin() function within the tKey instance's serviceProvider.

SubVerifierDetails

The triggerLogin function in TorusServiceProvider accepts the following parameters:

ParameterTypeDescriptionMandatory
typeOfLoginLOGIN_TYPEType of your login verifierYes
verifierstringVerifier Name from Web3Auth DashboardYes
clientIdstringClient ID from your login service providerYes
jwtParams?Auth0ClientOptionsAdditional JWT ParamsNo
hash?stringYour JWT in hashNo
queryParameters?TorusGenericObjectAdditional Query ParamsNo
customState?TorusGenericObjectAdditional Custom State ParamsNo

jwtParams

ParameterTypeDescriptionMandatory
domainstringDomain of your Auth0 App such as 'example.auth0.com'. Please use https://` as a prefixYes
client_id?stringThe Client ID found on your Auth0 Application settings pageYes
redirect_uri?stringThe default URL where Auth0 will redirect your browser to with the authentication result. It must be whitelisted in the "Allowed Callback URLs" field in your Auth0 Application's settings. If not provided here, it should be provided in the other methods that provide authentication.No
leeway?numberThe value in seconds used to account for clock skew in JWT expirations. Typically, this value is no more than a minute or two at maximum. Defaults to 60s.No
verifierIdField?stringThe field in jwt token which maps to verifier idNo
isVerifierIdCaseSensitive?booleanWhether the verifier id field is case sensitive. @defaultValue trueNo
id_token?stringPass on the id_token directly here. Useful in case of RWANo
access_token?stringPass on the access_token directly here.No
user_info_route?stringThe route for user info endpoint. This will be padded to domain. @defaultValue userinfoNo

typeOfLogin

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";
};

Usage

const loginResponse = await(tKey.serviceProvider as any).triggerLogin({
typeOfLogin: "google", // "google","facebook","reddit","discord","twitch", etc.
verifier: "google-tkey-w3a",
clientId: "774338308167-q463s7kpvja16l4l0kko3nb925ikds2p.apps.googleusercontent.com",
});
const user = loginResponse.userInfo;
console.log("User Details: ", user);

Get User Information

triggerLogin(params: SubVerifierDetails): Promise<TorusLoginResponse>;

The tKey Service Provider returns a TorusLoginResponse object which contains the user's information and details about the login. You can access the userInfo property within it to get the user details from the login provider.

TorusLoginResponse

ParameterTypeDescription
accessTokenstringUser Access Token
idToken?stringUser id_token
ref?stringUser refs
extraParams?stringAny extra parameters
emailstringUser Email ID
namestringUser Name
profileImagestringUser Profile Image
aggregateVerifier?stringAggregate Verifier Details
verifierstringVerifier Details
verifierIdstringVerifier ID
typeOfLoginLOGIN_TYPEType of Social Login

TorusKey

ParameterTypeDescription
publicAddressstringUser Public Address
privateKeystringUser Private Key
metadataNoncestringMetadata Nonce related to the user
typeOfUserenum("v1", "v2")User if v1 or v2
pub_key_XstringX coordinate of Public Key
pub_key_YstringY coordinate of Public Key

Trigger Login using Aggregate Verifier

triggerAggregateLogin()

await (tKey.serviceProvider as TorusServiceProvider).triggerAggregateLogin(AggregateLoginParams)

Takes in the aggregate verifier details as AggregateLoginParams.

interface AggregateLoginParams {
aggregateVerifierType: AGGREGATE_VERIFIER_TYPE;
verifierIdentifier: string;
subVerifierDetailsArray: SubVerifierDetails[];
}