Build: npm run build
Test: npm run test
npm i w3t
import {Web3Token} from "w3t";
import * as web3 from "web3";
//Mock the user in the crypto wallet. Use your web3provider install it.
const accounts = web3.eth.accounts.create();
// All users have the same msgMetadata
const msgMetadata = {
domain: "example.com",
statement: "Please Sign the Message.",
uri: "https://example.com",
// version: "1",
// chainId: 1
}
const w3tokenClient = new Web3Token(msgMetadata);
// Each user have different msgPayload
const msgPayload = {
address: accounts.address, // mock the user
// nonce, issuedAt, expirationTime
}
const message = w3tokenClient.prepareMessage(msgPayload, {expiresIn:'12h'});
//Invoke the wallet and sign the `message`
const signature = web3.eth.accounts.sign(message, accounts.privateKey);
const token = w3tokenClient.create(signature.signature);
//Set the token in request headers and request the server
//... headers:{'Authorization': `Bear ${token}`}
import {Web3Token} from "w3t";
//Get the token from the request headers
const token = req.headers.authorization.split(' ')[1]
//The server has the same msgMetadata
const msgMetadata = {
domain: "example.com",
statement: "Please Sign the Message.",
uri: "https://example.com",
// version: "1",
// chainId: 1
}
const w3tokenServer = new Web3Token(msgMetadata);
try{
const decoded_token = await w3tokenServer.verify(token, {maxAge:'3d'});
const addr = decoded_token.payload.address;
//...
}catch(e){
//verify failed
//...
}
import jwt from 'jsonwebtoken';
const token = req.headers.authorization.split(' ')[1];
const decoded_token = jwt.decode(token);
if (decoded_token.header.typ === 'w3t'){
// Web3 Token
} else if (decoded_token.header.typ ==='jwt'){
// JSON Web Token
} else{
// Invalid token
}
Web3Token uses the EIP-4361 format message, which can avoid Blind Message Attack.
Some fields of EIP-4361 are still in dispute, such as chainId. However, in order to be compatible with EIP-4361, Web3Token still retains these fields.
Web3Token divides the message into two parts: msgMetadata and msgPayload.
On a website, fields such as domain and statement are the same in different messages. We extract these static fields as msgMetadata and omit these fields in web3token. Variable fields such as address in each message constitute msgPayload.
The client and server have the same msgMetadata, which is loaded during the web3Token initialization. The preparemessage() and verify() will automatically load msgMetadata when executed.
{alg: "ECDSA", typ: "w3t"}
Name | EIP4361 Required | Web3Token Required | Example |
---|---|---|---|
domain |
required |
required |
example.com |
statement |
optional |
optional |
Please sign this message. |
uri |
required |
required |
https://example.com/login |
version |
required |
optional (default) |
default: 1 (MUST be 1) |
chainId |
required |
optional (default) |
default: 1
|
Name | EIP4361 Required | Web3Token Required | Example |
---|---|---|---|
address |
required |
required |
0x12345... |
nonce |
required |
optional (default) |
f040lq6s , default: siwe.generateNonce()
|
issued-at |
required |
optional (default) |
2021-09-30T16:25:24Z , default: now()
|
expiration-time |
optional |
optional (default) |
2021-09-30T22:25:24Z , default: now()+6h
|
not-before |
optional |
optional |
2021-09-30T16:25:24Z |
request-id |
optional |
optional |
1 |
resources |
optional |
optional |
https://example.com/my-web2-claim.json |
-
nonce
: The Web3Token does not required anonce
, but the EIP4361 required, so this implementation just retains the field. -
expiration-time
: EIP4361 does not required theexpiration-time
, but Web3Token required becauseexpiration-time
can resist Replay Attacks. -
nonce
,issued-at
andexpiration-time
have default values.
Name | Required | Description | Example |
---|---|---|---|
msgMetadata |
required |
The static fields in messages, see Table MsgMetadata. | { domain : 'example.com', statement : 'Please sign this message.', uri : 'localhost', version : 1, chainId : 1,} |
options |
optional |
default: {maxAge:7d, expiresIn:6h}
|
|
options.maxAge |
optional |
Server Only: the longest valid duration of the client-issued token. |
{maxAge:7d} , See ms.
|
options.expiresIn |
optional |
Client Only: the valid duration of the token. expiresIn MUST <= maxAge
|
{expiresIn:1d} See ms.
|
Name | Required | Description | Example |
---|---|---|---|
msgPayload |
required |
See Table MsgPayload
|
{address:0x12345... } |
options |
optional |
default: {expiresIn:6h}
|
|
options.expiresIn |
optional |
expiration-time = issued-at + expiresIn
|
1d , default: 6h
|
Type | Description | Example |
---|---|---|
string |
the message
|
example.com wants you to sign in with your Ethereum account:0x123456789... |
Name | Required | Description | Example |
---|---|---|---|
signature |
required |
the user signed the message of prepareMessage()
|
'0x98765... |
Type | Description | Example |
---|---|---|
string |
the token value |
aaaa.bbbb.cccc |
Name | Required | Description | Example |
---|---|---|---|
token |
required |
See Table MsgPayload
|
{address:0x12345... } or eyJzaWduYXR1cmUiOiI...
|
options |
optional |
default: {maxAge:7d}
|
|
options.maxAge |
optional |
maxAge >= issued-at - expiration-time
|
3d , default: 7d
|
Type | Description | Example |
---|---|---|
object |
the decoded token | {header:{alg: "ECDSA", typ: "web3token"},payload:{...MsgPayload},signature:'0x98765...'} |
- Blind Message Attacks
-
HTTPS
-
Replay Attacks