Log in with OAuth
To authenticate users using Single Page Application(SPA) flow, you can use the loginWithOAuth
method. This methods takes the OAuthLoginParams
as a parameter, which is an object that contains
the details of the verifier, and additional authentication 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.
Parameters
The OAuthLoginParams can be either a SubVerifierDetailsParams
or an
AggregateVerifierLoginParams
. If you wish to use a aggereate verifier, you can use the
AggregateVerifierLoginParams
, and for single verifier, you can use the SubVerifierDetailsParams
.
An aggregate verifier helps to generate a same account across multiple verifiers based on the JWT verification field. For instance if you are using Google, and Email Passwordless, and want to genetate the same account across both authentication method, you need to use an aggregate verifier.
SubVerifierDetailsParams
Contains the details of the verifier the app needs to use for authentication. You need 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. Available options are LOGIN_TYPE.GOOGLE , LOGIN_TYPE.FACEBOOK , LOGIN_TYPE.REDDIT , LOGIN_TYPE.DISCORD , LOGIN_TYPE.TWITCH , LOGIN_TYPE.APPLE , LOGIN_TYPE.GITHUB , LOGIN_TYPE.LINKEDIN , LOGIN_TYPE.TWITTER , LOGIN_TYPE.WEIBO , LOGIN_TYPE.LINE , and LOGIN_TYPE.PASSWORDLESS . |
verifier | Name/ Identifier of the verifier/ sub verifier in case of aggregate verifiers, you'd like your app to use for authentication. |
clientId | Client Id given by the auth provider. Pass a random string in case you're connecting to a JWT based setup. |
jwtParams? | Additional parameters for the Auth0 login provider. |
serverTimeOffset? | The server time offset in seconds. Default value is 0. |
export interface SubVerifierDetailsParams extends BaseLoginParams {
subVerifierDetails: SubVerifierDetails;
}
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 use for authentication. You need to use this in case of an aggregate verifier.
- Table
- Interface
Parameter | Description |
---|---|
aggregateVerifierIdentifier | The name of your aggregate verifier. Get it from the dashboard. |
subVerifierDetailsArray | An array containing the details of your sub verifiers. |
aggregateVerifierType? | Defines the type of aggregation required for your aggregate verifier. You need to use single_id_verifier by default in most cases. This parameter allows you define the number of verifier ids to be used for aggregation. |
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
Single Verifier
To login with a single verifier, you will require to create a custom verifier in the Web3Auth dashboard. If you haven't already created one, learn how to create a verifier.
import { SubVerifierDetailsParams } from "@web3auth/mpc-core-kit";
const verifierConfig: SubVerifierDetailsParams = {
subVerifierDetails: {
typeOfLogin: "google",
verifier: "w3a-google-demo",
clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com",
},
};
await coreKitInstance.loginWithOAuth(verifierConfig);
Aggregate Verifier
To login with an aggregate verifier, you will require to create an aggregate verifier in the Web3Auth dashboard. If you haven't already created one, learn how to create an aggregate verifier.
import { AggregateVerifierLoginParams } from "@web3auth/mpc-core-kit";
const verifierConfig: AggregateVerifierLoginParams = {
aggregateVerifierIdentifier: "aggregate-sapphire",
subVerifierDetailsArray: [
{
typeOfLogin: "google",
verifier: "w3a-google",
clientId: "774338308167-q463s7kpvja16l4l0kko3nb925ikds2p.apps.googleusercontent.com",
},
],
};
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.
import { SubVerifierDetailsParams } from "@web3auth/mpc-core-kit";
const verifierConfig: SubVerifierDetailsParams = {
subVerifierDetails: {
typeOfLogin: "google",
verifier: "w3a-google-demo",
clientId: "519228911939-cri01h55lsjbsia1k7ll6qpalrus75ps.apps.googleusercontent.com",
},
importTssKey: "SECP256K1_PRIVATE_KEY_OR_ED25519_SEED",
};
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, you 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.