Read Access Control
In this tutorial, we will cover how to authenticate a user to enforce read access control. This tutorial expands on the schema created in the Write Access Control tutorial.
In Kwil, data is primarily read with view
procedures. For executing view
procedures, the same access control rules are available as for write procedures.
Additionally, Kwil supports two authentication methods for enforcing read access control:
The main difference between Private RPC and Kwil Gateway is that Private RPC requires the user to sign for authentication with each call request, whereas the Kwil Gateway enables a user to sign once for all call requests in a session.
Private RPC Mode
Private RPC is a server-side configuration in kwild
that enforces user authentication for each call request. Learn more about private mode here.
By default, data in Kwil is public. To enable private data and read access control, Private RPC mode requires a user to identify themselves via signing a message with each call request. If the signature is valid, the call will return the query result. If the signature is invalid or not provided, the call will return an error.
Private RPC mode is not enabled on the Longhorn Testnet. If you would like to test private mode, run a local node with private RPC enabled.
If your Kwil network is running in private mode, authenticate users by passing a KwilSigner
to the call
method.
- Browser
- NodeJS
import { WebKwil, KwilSigner } from '@kwilteam/kwil-js';
import { BrowserProvider } from 'ethers';
const kwil = new WebKwil({
kwilProvider: "http://localhost:8484",
chainId: "your-chain-id",
});
async function getSigner() {
// Prompt user to login with their wallet
const provider = new BrowserProvider(window.ethereum);
const ethSigner = await provider.getSigner();
const ethAddress = await ethSigner.getAddress();
return new KwilSigner(ethSigner, ethAddress);
}
async function authenticatedProcedure() {
const kwilSigner = await getSigner();
const payload = {
name: "get_profile",
dbid: 'x123abc...'
}
return await kwil.call(payload, kwilSigner); // user will be prompted to authenticate
}
import { NodeKwil } from '@kwilteam/kwil-js';
import { Wallet } from 'ethers';
const kwil = new NodeKwil({
kwilProvider: "http://localhost:8484",
chainId: "your-chain-id",
});
async function getSigner() {
// Use private key to authenticate
const provider = new Wallet("users-private-key");
const ethSigner = await provider.getSigner();
const ethAddress = await ethSigner.getAddress();
return new KwilSigner(ethSigner, ethAddress);
}
async function authenticatedProcedure() {
const kwilSigner = await getSigner();
const payload = {
name: "get_profile",
dbid: 'x123abc...'
}
return await kwil.call(payload, kwilSigner);
}
Kwil Gateway
The Kwil Gateway requires a user to sign a message with their private key. Once the signature is verified, the gateway will return the user a cookie to be passed with all subsequent requests.
The gateway is still in private beta. If you would like access, please contact our team.
KGW cookies are partitioned by account and origin. This means that multiple users can be authenticated at the same time, and users authenticated from one origin (e.g., https://website1.com
) will not be authenticated on another origin (e.g., https://website2.com
).
Require Authentication in Kuneiform
To signal to the Kwil Gateway that a procedure requires authentication, add an annotation before the procedure. The @kgw(authn='true')
annotation will require the user to authenticate via a signature before executing the procedure.
// authentication required view procedure to reveal a user profile
@kgw(authn='true')
procedure get_profile () public view returns table(id int, username text, age int) {
return SELECT id, username, age
FROM users
WHERE address = @caller;
}
The @kgw(authn='true')
annotation will only require the user to authenticate IF the Kwil Gateway is enabled. If the gateway is not enabled, the annotation will be ignored.
Authenticating a User
Using the JavaScript SDK and the KwilSigner, a user can authenticate with the Kwil Gateway (KGW). When calling the call
method, the Kwil SDK will check if the gateway is enforcing authentication. If authentication is enforced, the gateway will prompt the user to sign a message with their account keys. Once the signature is verified, the SDK will include a cookie to be passed with all subsequent requests.
When multiple users are authenticated, the current user is the signer passed to the call
method.
- Browser
- NodeJS
import { WebKwil, KwilSigner } from '@kwilteam/kwil-js';
import { BrowserProvider } from 'ethers';
const kwil = new WebKwil({
kwilProvider: "your-kgw-url",
chainId: "your-chain-id",
});
async function getSigner() {
// Prompt user to login with their wallet
const provider = new BrowserProvider(window.ethereum);
const ethSigner = await provider.getSigner();
const ethAddress = await ethSigner.getAddress();
return new KwilSigner(ethSigner, ethAddress);
}
async function authenticatedProcedure() {
const kwilSigner = await getSigner();
const payload = {
name: "get_profile",
dbid: 'x123abc...'
}
return await kwil.call(payload, kwilSigner); // user will be prompted to authenticate
}
import { NodeKwil } from '@kwilteam/kwil-js';
import { Wallet } from 'ethers';
const kwil = new NodeKwil({
kwilProvider: "your-kgw-url",
chainId: "your-chain-id",
});
async function getSigner() {
// Use private key to authenticate
const provider = new Wallet("your_private_key");
const ethSigner = await provider.getSigner();
const ethAddress = await ethSigner.getAddress();
return new KwilSigner(ethSigner, ethAddress);
}
async function authenticatedProcedure() {
const kwilSigner = await getSigner();
const payload = {
name: "get_profile",
dbid: 'x123abc...'
}
return await kwil.call(payload, kwilSigner); // user will be prompted to authenticate
}
Logging Out a User
Logging out a user invalidates the cookie from the requesting origin. This means that the user will need to re-authenticate to access any procedures that require authentication.
Log out all signers
To log out all signers that a user has authenticated with, call the logout
method with no arguments.
async function logout() {
await kwil.auth.logoutKGW();
}
Log out a specific signer
To log out a specific signer, pass the signer to the logout
method.
async function logoutUser() {
const kwilSigner = await getSigner();
await kwil.auth.logoutKGW(kwilSigner);
}
Advanced Usage
Disable Auto Authentication
By default, the Kwil SDK will automatically prompt the user to authenticate when required (for both Private RPC and Kwil Gateway). To disable this behavior, set the autoAuthenticate
option to false
.
const kwil = new WebKwil({
kwilProvider: "http://localhost:8484",
chainId: "your-chain-id",
autoAuthenticate: false,
});
Manually Authenticate
To manually authenticate a user, call the authenticatePrivateMode
or authenticateKGW
methods.
- Private RPC
- Kwil Gateway
Call the authenticatePrivateMode
method to authenticate with the Private RPC. Pass the AuthBody
that is returned from the authenticatePrivateMode
method to the call
method.
async function manualAuthenticate() {
const authBody = await kwil.auth.authenticatePrivateMode(kwilSigner); // user will be prompted to authenticate
const payload = {
name: "get_profile",
dbid: 'x123abc...',
authBody
};
return await kwil.call(payload);
}
Call the authenticateKGW
method to authenticate with the Kwil Gateway.
async function manualAuthenticate() {
await kwil.auth.authenticateKGW(kwilSigner);
}
By default, the browser will automatically store the cookie in the browser's cookie jar and pass it with all subsequent requests. In NodeJS, you must manually pass the cookie.
async function authenticatedProcedure() {
const auth = await kwil.auth.authenticateKGW(kwilSigner);
const cookie = auth.data?.cookie;
const payload = {
name: "get_profile",
dbid: 'x123abc...',
cookie
}
return await kwil.call(payload); // user will not be prompted to authenticate
}