How To Build A Web3 Auth Login In Angular

How To Build A Web3 Auth Login In Angular

What is Web3 Login?

Web3 Login is a common and crucial feature to have in an application with public data. Sometimes the term Web3 authentication is used instead of Web3 login. Web3 login increases security by one more level.

Step-1:

Create an angular project in terminal by using following command.

ng new web3-login

Now go into the project Directory(web3-login) and do ng serve and then open webpage with localhost:4200.

output of angular project in web.

Step-2:

Now install web3.js and web3auth by using following commands

npm install --save web3
npm install --save @web3auth/modal @web3auth/base

Step-3:

Add the below code to execute login, logout, transactions, fetch user data and so on... Change the code depending on your requirement.

app.component.ts:

import { Component } from "@angular/core";
import { Web3Auth } from "@web3auth/modal";
import { CHAIN_NAMESPACES, SafeEventEmitterProvider } from "@web3auth/base";
import RPC from "./web3RPC";
const clientId = "YOUR_WEB3AUTH_CLIENT_ID"; // get from     
https://dashboard.web3auth.io

@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})

export class AppComponent {
 title = "angular-app";
 web3auth: Web3Auth | null = null;
 provider: SafeEventEmitterProvider | null = null;
 isModalLoaded = false;

async ngOnInit() {
  this.web3auth = new Web3Auth({
    clientId,
    chainConfig: {
      chainNamespace: CHAIN_NAMESPACES.EIP155,
      chainId: "0x1",
      rpcTarget: "https://rpc.ankr.com/eth", // This is the public RPC we have added, please pass on your own endpoint while creating an app
    },
  });
  const web3auth = this.web3auth;


  await web3auth.initModal();
  if (web3auth.provider) {
    this.provider = web3auth.provider;
  }
  this.isModalLoaded = true;
}

login = async () => {
if (!this.web3auth) {
  console.log("web3auth not initialized yet");
  return;
}
const web3auth = this.web3auth;
this.provider = await web3auth.connect();
console.log("logged in");
};

getUserInfo = async () => {
  if (!this.web3auth) {
    console.log("web3auth not initialized yet");
    return;
  }
  const user = await this.web3auth.getUserInfo();
  console.log(user);
};

logout = async () => {
  if (!this.web3auth) {
    console.log("web3auth not initialized yet");
    return;
  }
  await this.web3auth.logout();
  this.provider = null;
  console.log("logged out");
};

getChainId = async () => {
  if (!this.provider) {
    console.log("provider not initialized yet");
    return;
  }
  const rpc = new RPC(this.provider);
  const chainId = await rpc.getChainId();
  console.log(chainId);
};
getAccounts = async () => {
  if (!this.provider) {
    console.log("provider not initialized yet");
    return;
  }
  const rpc = new RPC(this.provider);
  const address = await rpc.getAccounts();
  console.log(address);
};

getBalance = async () => {
  if (!this.provider) {
    console.log("provider not initialized yet");
    return;
  }
  const rpc = new RPC(this.provider);
  const balance = await rpc.getBalance();
  console.log(balance);
};

sendTransaction = async () => {
  if (!this.provider) {
    console.log("provider not initialized yet");
    return;
  }
  const rpc = new RPC(this.provider);
  const receipt = await rpc.sendTransaction();
  console.log(receipt);
};

signMessage = async () => {
  if (!this.provider) {
    console.log("provider not initialized yet");
    return;
  }
  const rpc = new RPC(this.provider);
  const signedMessage = await rpc.signMessage();
  console.log(signedMessage);
};

getPrivateKey = async () => {
  if (!this.provider) {
    console.log("provider not initialized yet");
    return;
  }
  const rpc = new RPC(this.provider);
  const privateKey = await rpc.getPrivateKey();
  console.log(privateKey);
};
}

In the above example, add the clientId from web3 auth dashboard.Go to dashboard and then create a project in web3 auth and we will be getting clientId. Add that in the above example and track the logged in users.

package.json:

Change your package file by adding web3 and web3 auth.

{
"name": "angular-web3auth-modal",
"version": "0.1.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "~13.2.4",
"@angular/common": "~13.2.4",
"@angular/compiler": "~13.2.4",
"@angular/core": "~13.2.4",
"@angular/forms": "~13.2.4",
"@angular/platform-browser": "~13.2.4",
"@angular/platform-browser-dynamic": "~13.2.4",
"@angular/router": "~13.2.4",
"@web3auth/base": "^3.0.0",
"@web3auth/modal": "^3.0.3",
"web3": "^1.7.0",
"assert": "^2.0.0",
"crypto-browserify": "^3.12.0",
"https-browserify": "^1.0.0",
"os-browserify": "^0.3.0",
"process": "^0.11.10",
"rxjs": "~7.5.4",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"tslib": "^2.3.1",
"url": "^0.11.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.2.5",
"@angular/cli": "~13.2.5",
"@angular/compiler-cli": "~13.2.4",
"@types/jasmine": "~3.10.3",
"@types/keccak": "^3.0.1",
"@types/node": "^12.11.1",
"@types/readable-stream": "^2.3.13",
"jasmine-core": "~4.0.1",
"karma": "~6.3.16",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.5.5"
}
}

Create a file name called web3RPC.ts

import type { SafeEventEmitterProvider } from "@web3auth/base";
import Web3 from "web3";
export default class EthereumRpc {
private provider: SafeEventEmitterProvider;

constructor(provider: SafeEventEmitterProvider) {
this.provider = provider;
}
async getChainId(): Promise<string> {
try {
const web3 = new Web3(this.provider as any);

// Get the connected Chain's ID
const chainId = await web3.eth.getChainId();

return chainId.toString();
} catch (error) {
return error as string;
}
}
async getAccounts(): Promise<any> {
try {
const web3 = new Web3(this.provider as any);

// Get user's Ethereum public address
const address = (await web3.eth.getAccounts())[0];

return address;
} catch (error) {
return error;
}
}
async getBalance(): Promise<string> {
try {
const web3 = new Web3(this.provider as any);

// Get user's Ethereum public address
const address = (await web3.eth.getAccounts())[0];

// Get user's balance in ether
const balance = web3.utils.fromWei(
await web3.eth.getBalance(address) // Balance is in wei
);

return balance;
} catch (error) {
return error as string;
}
}
async sendTransaction(): Promise<any> {
try {
const web3 = new Web3(this.provider as any);

// Get user's Ethereum public address
const fromAddress = (await web3.eth.getAccounts())[0];

const destination = fromAddress;

const amount = web3.utils.toWei("0.001"); // Convert 1 ether to wei

// Submit transaction to the blockchain and wait for it to be mined
const receipt = await web3.eth.sendTransaction({
from: fromAddress,
to: destination,
value: amount,
maxPriorityFeePerGas: "5000000000", // Max priority fee per gas
maxFeePerGas: "6000000000000", // Max fee per gas
});

return receipt;
} catch (error) {
return error as string;
}
}
async signMessage() {
try {
const web3 = new Web3(this.provider as any);

// Get user's Ethereum public address
const fromAddress = (await web3.eth.getAccounts())[0];

const originalMessage = "YOUR_MESSAGE";

// Sign the message
const signedMessage = await web3.eth.personal.sign(
originalMessage,
fromAddress,
"test password!" // configure your own password here.
);

return signedMessage;
} catch (error) {
return error as string;
}
}
async getPrivateKey(): Promise<any> {
try {
const privateKey = await this.provider.request({
method: "eth_private_key",
});

return privateKey;
} catch (error) {
return error as string;
}
}
}

app.component.html:

<div class="content">
<h1 class="title"><a target="_blank" href="http://web3auth.io/">Web3Auth</a> & AngularJS Example</h1>
<div class="setting">
<div *ngIf="isModalLoaded && provider; else elseBlock" class="card-container">
<button class="card card-small" (click)="getUserInfo()">Get User Info</button>
<button class="card card-small" (click)="getChainId()">Get Chain ID</button>
<button class="card card-small" (click)="getAccounts()">Get Accounts</button>
<button class="card card-small" (click)="getBalance()">Get Balance</button>
<button class="card card-small" (click)="sendTransaction()">Send Transaction</button>
<button class="card card-small" (click)="signMessage()">Sign Message</button>
<button class="card card-small" (click)="getPrivateKey()">Get Private Key</button>
<button class="card card-small" (click)="logout()">Log Out</button>
<div class="console" id="console-ui">
<p class="code">Logged In</p>
</div>
</div>
<ng-template #elseBlock>
<div class="card-container">
<button class="card card-small" (click)="login()">Login</button>
</div>
</ng-template>
</div>
</div>

polyfills.ts:

import 'zone.js'; // Included with Angular CLI.

(window as any).global = window;
global.Buffer = global.Buffer || require('buffer').Buffer;
global.process = global.process || require('process');

tsconfig.json:

{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"skipLibCheck": true,
"target": "es2017",
"module": "es2020",
"lib": [
"es2020",
"dom"
],
"paths":{
"crypto": ["./node_modules/crypto-browserify"],
"stream": ["./node_modules/stream-browserify"],
"assert": ["./node_modules/assert"],
"http": ["./node_modules/stream-http"],
"https": ["./node_modules/https-browserify"],
"os": ["./node_modules/os-browserify"],
"process": ["./node_modules/process"],
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

app.component.css:

:host {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
font-size: 14px;
color: #333;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.title {
font-size: 3rem;
}

.title a {
color: #0070f3;
text-decoration: none;
}

.content {
display: flex;
margin: 82px auto 32px;
padding: 0 16px;
max-width: 960px;
flex-direction: column;
align-items: center;
}

.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-top: 16px;
}

.card {
all: unset;
border-radius: 4px;
border: 1px solid #0070f3;
color: #0070f3;
height: 40px;
width: 150px;
margin: 0 8px 16px;
padding: 16px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
line-height: 24px;
}

.card.card-small {
height: 16px;
width: 128px;
cursor: pointer;
}

.card-container .card:not(.highlight-card):hover {
transform: translateY(-3px);
box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
}

.grid {
display: flex;
align-items: center;
flex-direction: column;
}

.console {
width: 100%;
}

.code {
background: #fafafa;
border-radius: 5px;
font-size: 1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
overflow-wrap: break-word;
padding: 1rem;
}

.footer {
display: flex;
flex: 1;
padding: 2rem 0;
border-top: 1px solid #eaeaea;
justify-content: center;
align-items: center;
margin-top: 10rem;
}

.footer a {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}

.logo {
height: 1em;
height: 1.5rem;
margin-left: 0.5rem;
}

After adding all these things you will get one error. To rectify that, go to angular.json file and enable "true" for both "buildOptimizer" and "aot".

Output page after adding web3 login

After login you can fetch whatever data you need.

output page after login

This is how we can build web3 login in angular applications.

Thank you!!!