# Connect FullAccess account with near-api-js

Published 2022-06-12

In this article, we will see how a user can sign in with a FullAccess key to your dApp, but also why that is probably not a good idea.


# How connecting your wallet works

NEAR Protocol allows a single account to have multiple keys with different permissions. It currently supports two levels of permissions for keys:

  • Full access keys allow a user to perform any action, including transferring NEAR, calling smart contracts, adding more keys and deploying smart contract code.
  • Function access keys are a lot more limited. They can only be used to call the specified function, it is restricted to 0.25N which it may only use for gas fees and cannot attach it to any function calls. It can also be more narrowly specified to only be allowed to call certain functions on the smart contract.

This allows for a greater level of security, compared to using a single all-powerful private key to interact with dApps.

near-api-js, most commonly used in web-apps to connect wallets and interact with smart contracts uses a function access key when a user connects their wallet. The common authentication flow is:

  1. User clicks Connect Wallet button
  2. near-api-js is used to create a wallet object and call request sign in
  3. The user is redirected to the NEAR Wallet site to authorise the login
  4. The user is redirected back to the original site

What happens behind the scenes, is that at step 2, near-api-js generates a new key pair (a public key and a private key) and stores it in the browser’s local storage as a pending access key. It then passes the public key to the wallet account, where the user (in step 3), uses their FullAccess key stored in the wallet site’s local storage to add the new key to their account. After being redirected, in step 4, near-api-js marks the pending key as accepted in local storage. That point onwards, that key is used in the wallet to authenticate the user.

The code that triggers this usually looks something like this:

await wallet.requestSignIn({
  contractId: "your-contract.near",
  methodNames: [""], // Optional, if ommitted all methods can be called
  successUrl: "<https://your-website.io/success>", // Optional, if ommitted, the user will be redirected to the current page
  failureUrl: "<https://your-website.io/failure>", // Optional, same as success
});

# Signing in with a FullAccess key

This sign-in flow doesn’t support requesting full access keys. A contract must be specified for keys to be created, and the keys will then be scoped only to this contract.

This however doesn’t mean it is not possible to sign in using FullAccess keys. Although near-api-js doesn’t support it out of the box, it is possible to create a pair of keys and call the wallet ourselves, and get when redirected get the wallet to mark the keys we created as accepted.

We need to create our own implementation of of the sign in method, that creates a FullAccess key.

It looks something like this:

const PENDING_ACCESS_KEY_PREFIX = "pending_key";

const loginFullAccess = async (options) => {
  const currentUrl = new URL(window.location.href);
  const newUrl = new URL(wallet._walletBaseUrl + "/login/");
  newUrl.searchParams.set("success_url", options.successUrl || currentUrl.href);
  newUrl.searchParams.set("failure_url", options.failureUrl || currentUrl.href);

  const accessKey = KeyPair.fromRandom("ed25519");
  newUrl.searchParams.set("public_key", accessKey.getPublicKey().toString());
  await wallet._keyStore.setKey(
    wallet._networkId,
    PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(),
    accessKey
  );

  window.location.assign(newUrl.toString());
};

Let’s examine this function in sections:

First we need to declare a constant. This is the same prefix used by the wallet to mark keys as pending. As it is not exposed, we need to create it ourselves, and make sure it matches the value in the API.

const PENDING_ACCESS_KEY_PREFIX = "pending_key";

Next we start to construct the wallet URL where to user will be redirected to create the key

const currentUrl = new URL(window.location.href);
const newUrl = new URL(wallet._walletBaseUrl + "/login/");
newUrl.searchParams.set("success_url", options.successUrl || currentUrl.href);
newUrl.searchParams.set("failure_url", options.failureUrl || currentUrl.href);

After that, we generate a new ed25519 public-private key pair, and add it to the URL:

const accessKey = KeyPair.fromRandom("ed25519");
newUrl.searchParams.set("public_key", accessKey.getPublicKey().toString());

We also add it to the wallet as a pending key:

await wallet._keyStore.setKey(
  wallet._networkId,
  PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(),
  accessKey
);

Finally, we redirect the user to the wallet site to add the key to their account

window.location.assign(newUrl.toString());

Notice, how we didn’t have to specify that the key will be FullAccess, but when the wallet site is called, it is asking for the key to be added as FullAccess. The reason behind this, is that FullAccess is the default behaviour when redirecting to the wallet, and only if a contract is specified will it be created as a function access key. Also, this snippet assumes that you already have a wallet object. This was created by earlier when NEAR was configured with code like this:

wallet = new WalletConnection(near, "your-dapp")

# Things to keep in mind

In all but the most rarest cases, function access key is perfectly sufficient for your dApp. As discussed earlier, although the key generated is limited to a single function, that doesn’t mean that your dApp will only be able to interact with that function.

This also justifies why this is not supported by the near-js-api.

A full access key has complete control over the account, which when compromised can lead to users loosing all NEAR, tokens and NFTs.

However, in some specific cases you may require a full access key, such as allowing a user to create sub-accounts, or deploying contracts. For those cases, the above snippet will allow you to do that.