Authentication

Typically, you will not need authentication because these are features that are pretty specific to the BitBadges website. However, in some cases, you may.

The Blockin execution flow is simple:

1) Fetch Challenge

Request a challenge from the POST /api/v0/auth/getChallenge route.

const res = await BitBadgesApi.getSignInChallenge({
    chain: "Ethereum",
    address: "0x.....",
    // hours: 168
})

/*
    res = {
        nonce: "...",
        params: {
            address: "0x....",
            expirationDate: "...",
            ...
        }, 
        "blockinMessage": "https://bitbadges.io wants you to sign in with your Ethereum account...."
    }
*/

1.5) Edit Challenge

The challenge returned by the API is a base challenge. You can edit certain fields if desired (like resources aka scopes or expiration time). Certain fields though like uri, domain, statement, nonce must remain consistent.

For scopes, we use the resources field. Specify the following strings (in full) directly in the resources fields for each authorized scope you want. Full Access overrides all of them. Note that we are looking to fine-grain the scopes further in the future. See https://github.com/BitBadges/bitbadges-indexer/blob/master/src/blockin/scopes.ts for the up to date values.

// We use a "Label : Explanation" format for the scopes
const SupportedScopes = [
  'Full Access: Full access to all features.',
  'Report: Report users or collections.',
  'Reviews: Create, read, update, and delete reviews.',

  'Read Profile: Read your private profile information. This includes your email, approved sign-in methods, connections, and other private information.',
  'Update Profile: Update your user profile information. This includes your email, approved sign-in methods, connections, and other private information, as well as your public facing profile.',

  'Read Address Lists: Read private address lists on behalf of the user.',
  'Create Address Lists: Create new address lists on behalf of the user (private or public).',
  'Update Address Lists: Update address lists on behalf of the user.',
  'Delete Address Lists: Delete address lists on behalf of the user.',

  'Create Auth Codes: Create new authentication codes on behalf of the user.', //Still need signature for this
  'Read Auth Codes: Read authentication codes on behalf of the user.',
  'Delete Auth Codes: Delete authentication codes on behalf of the user.',

  'Send Claim Alerts: Send claim alerts on behalf of the user.',
  'Read Claim Alerts: Read claim alerts on behalf of the user. Note that claim alerts may contain sensitive information like claim codes, secret IDs, etc.',

  'Create Secrets: Create new secrets on behalf of the user.',
  'Read Secrets: Read secrets on behalf of the user.',
  'Delete Secrets: Delete secrets on behalf of the user.',
  'Update Secrets: Update secrets on behalf of the user.',

  'Read Private Claim Data: Read private claim data on behalf of the user (e.g. codes, passwords, private user lists, etc.).'
];

2) Sign Challenge

The user should then sign the blockinMessage string with a personal message signature from their wallet. See https://github.com/BitBadges/bitbadges-frontend/tree/main/src/bitbadges-api/contexts/chains for complete code snippets.

Ethereum:

const signChallenge = async (message: string) => {
    const sign = await signMessage({
      message: message,
    })

    const msgHash = ethers.utils.hashMessage(message)
    const msgHashBytes = ethers.utils.arrayify(msgHash)
    const pubKey = ethers.utils.recoverPublicKey(msgHashBytes, sign)

    const pubKeyHex = pubKey.substring(2)
    const compressedPublicKey = Secp256k1.compressPubkey(
      new Uint8Array(Buffer.from(pubKeyHex, "hex"))
    )
    const base64PubKey = Buffer.from(compressedPublicKey).toString("base64")
    setPublicKey(cosmosAddress, base64PubKey)
    setCookies("pub_key", `${cosmosAddress}-${base64PubKey}`, { path: "/" })

    return {
      message,
      signature: sign,
    }
  }

Cosmos (Keplr):

const signChallenge = async (message: string) => {
    let sig = await window.keplr?.signArbitrary("bitbadges_1-2", cosmosAddress, message);

    if (!sig) sig = { signature: '', pub_key: { type: '', value: '' } };

    const signatureBuffer = Buffer.from(sig.signature, 'base64');
    const uint8Signature = new Uint8Array(signatureBuffer); // Convert the buffer to an Uint8Array
    const pubKeyValueBuffer = Buffer.from(sig.pub_key.value, 'base64'); // Decode the base64 encoded value
    const pubKeyUint8Array = new Uint8Array(pubKeyValueBuffer); // Convert the buffer to an Uint8Array

    const isRecovered = verifyADR36Amino('cosmos', cosmosAddress, message, pubKeyUint8Array, uint8Signature, 'secp256k1');
    if (!isRecovered) {
      throw new Error('Signature verification failed');
    }

    return {
      message: message,
      signature: sig.pub_key.value + ':' + sig.signature,
    }
  }

Solana (Phantom Wallet):

const getProvider = () => {
  if ('phantom' in window) {
    const phantomWindow = window as any;
    const provider = phantomWindow.phantom?.solana;
    setSolanaProvider(provider);
    if (provider?.isPhantom) {
      return provider;
    }

    window.open('https://phantom.app/', '_blank');
  }
};

const signChallenge = async (message: string) => {
  const encodedMessage = new TextEncoder().encode(message);
  const provider = solanaProvider;
  const signedMessage = await provider.request({
    method: "signMessage",
    params: {
      message: encodedMessage,
      display: "utf8",
    },
  });
  
  return { message: message, signature: signedMessage.signature };
}

Bitcoin (Phantom Wallet):

const getProvider = () => {
  if ('phantom' in window) {
    const phantomWindow = window as any;
    const provider = phantomWindow.phantom?.bitcoin;
    if (provider?.isPhantom) {
      return provider;
    }

    window.open('https://phantom.app/', '_blank');
  }
};

function bytesToBase64(bytes: Uint8Array) {
  const binString = String.fromCodePoint(...bytes);
  return btoa(binString);
}

const signChallenge = async (message: string) => {
  const encodedMessage = new TextEncoder().encode(message);
  const provider = getProvider();
  const { signature } = await provider.signMessage(address, encodedMessage);

  return { message: message, signature: bytesToBase64(signature) };
};

3) Send and Verify Signed Challenge

Send the signed message via POST /api/v0/auth/verify. This will grant a Express.js session cookie which is valid for whatever amount of hours you specify in the request. The params should match match what is returned from Step 2.

const res = await BitBadgesApi.verifySignIn({
    chain: "Ethereum",
    message: "https://bitbadges.io wants you to sign in with your....",
    signature: "...."
})

//console.log(res.success) 

4) Check health of sign in

At any time, you can check the health of the signin by POST /api/v0/auth/status.

const res = await BitBadgesApi.checkIfSignedIn(requestBody)
//console.log(res.signedIn)

Last updated