Skip to main content

Central Authentication Service SDK

Introduction

This SDK is used as a client for interfacing with the Shib Central Authentication Service (CAS). This service is a decentralized authentication system that leverages Ethereum addresses and cryptographic signatures for secure user authentication. It ensures unique request validation and signature verification, making it suitable for applications requiring blockchain-based authentication.

The service supports replay attack prevention through unique request IDs and verifies user identities by recovering addresses from Ethereum signatures.

The Shib Central Authentication Service is responsible for generating secure, signed JSON Web Tokens (JWT) using the ES512 (ECDSA with SHA-512) algorithm with expiration.

The SDK also supports account abstraction.

Audience

Developers.

Installation

Prerequisites

  • Your wallet address must have a valid DID registered to use CAS.
  • Addresses with valid DIDs can use this SDK.

Install the SDK

Type the following command to install the SDK.

pnpm i @shibaone/cas-sdk

Local Development

Install Deps -> Build -> Publish Locally

Install Dependencies

pnpm install

Build

Build with package at /lib with vite:

pnpm build:vite

Publish Locally

If npm package not available. First install yalc, publish the package -

yalc push --sig
note

Use Vite in dependent projects for a smoother experience as Vite has native WASM support.

Testing

Set up .env file by referring .example.env:

cp .env.example .env

Run the tests:

pnpm test

To view coverage report, open coverage/lcov-report/index.html in any browser.

note

Check test/cas.test.ts on how to use the SDK with testnet.

Getting Started

  1. Import CAS-SDK
  2. Import signer from ShibAuthSDK/ethers.js
  3. Create an instance of CAS-SDK
  4. Call authenticate method of CAS-SDK
  5. The authenticate method returns a JWTToken.
import { ethers } from "ethers";
import { CASDK } from "CASDK";

const signer = new ethers.Wallet(privateKey, provider);

const caSDK = await CASDK.create({
signer,
});

const jwtToken = await caSDK.authenticate();

Types

Here are the types that appear in this SDK:

type SDKCreationParams = {
signer: Signer;
casURL?: string;
};

type DecodedToken = {
did: string;
exp: number;
};

type JWKS = {
keys: [
{
kty: string;
use: string;
key_ops: [string];
alg: string;
kid: string;
crv: string;
x: string;
y: string;
}
];
};

type AuthorizeParams = {
secretKey: string;
authenticateJwtToken: string;
casUrl?: string;
}

Constants

Constants used in this SDK:

const CAS_URL = "https://cas.shibinternal.com";

Utils

authorize

async function authorize (params: AuthorizeParams)

An utility function used to authorize the user for allowing the access with our SDKs services. It MUST be used on your Backend.

Example:

import { CAS_URL } from '@shibaone/cas-sdk/costants'
import { authorize } from '@shibaone/cas-sdk/utils'

const jwt = await autorize({
secretKey: "Your secret key retrieved by the Shiba Developer Portal"
authenticateJwtToken: "Your JWT token retrived from the method authenticate in the client side"
casUrl: CAS_URL
})
caution

Always keep your secretKey secure and never expose it on the client side.

Integrate the authorization process in other languages

If you're not using Node.js as your backend, you can still integrate the authorization process in other languages. Below are some language-specific examples to help you implement it:

python
import requests

class AuthorizeParams:
def __init__(self, secret_key, authenticate_jwt_token, cas_url=None):
self.secret_key = secret_key
self.authenticate_jwt_token = authenticate_jwt_token
self.cas_url = cas_url

def authorize(params: AuthorizeParams) -> str:
try:
headers = {
"x-secret-key": params.secret_key,
"Authorization": f"Bearer {params.authenticate_jwt_token}",
}
url = f"{params.cas_url}/api/v1/authorize"
response = requests.post(url, headers=headers)
response.raise_for_status()
jwt_token = response.json().get("jwtToken")
return jwt_token
except Exception as e:
raise RuntimeError(f"authenticate: {str(e)}")
go
package main

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
)

type AuthorizeParams struct {
SecretKey string
AuthenticateJwtToken string
CasUrl string
}

func Authorize(params AuthorizeParams) (string, error) {
url := fmt.Sprintf("%s/api/v1/authorize", params.CasUrl)
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte("{}")))
if err != nil {
return "", fmt.Errorf("authenticate: %v", err)
}
req.Header.Set("x-secret-key", params.SecretKey)
req.Header.Set("Authorization", "Bearer "+params.AuthenticateJwtToken)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("authenticate: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("authenticate: status %v", resp.Status)
}

var result map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return "", fmt.Errorf("authenticate: %v", err)
}

jwtToken, ok := result["jwtToken"].(string)
if !ok {
return "", errors.New("authenticate: invalid jwtToken")
}

return jwtToken, nil
}
Java
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.util.Map;
import com.google.gson.Gson;

public class AuthorizeParams {
public String secretKey;
public String authenticateJwtToken;
public String casUrl;

public AuthorizeParams(String secretKey, String authenticateJwtToken, String casUrl) {
this.secretKey = secretKey;
this.authenticateJwtToken = authenticateJwtToken;
this.casUrl = casUrl;
}
}

public class Authorize {
public static String authorize(AuthorizeParams params) throws Exception {
String urlString = params.casUrl + "/api/v1/authorize";
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("x-secret-key", params.secretKey);
conn.setRequestProperty("Authorization", "Bearer " + params.authenticateJwtToken);
conn.setDoOutput(true);

// Send an empty body
try (OutputStream os = conn.getOutputStream()) {
os.write("{}".getBytes());
os.flush();
}

int responseCode = conn.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new Exception("authenticate: HTTP error code " + responseCode);
}

try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(br, Map.class);
return (String) response.get("jwtToken");
}
}
}

Methods

Constructor

constructor(signer: Signer, casURL: string)

Description

  • Initialises a new instance of the CASDK class with a provided Signer object.
  • A Signer is an abstraction of an Ethereum Account (Externally Owned Account-EOA), which can be used to sign messages and transactions and send signed transactions to the Blockchain Network to execute state changing operations.
  • casURL the endpoint of Shib Central Authentication Service.

Returns

  • The above method returns an instance of the CASDK.

Example:

  import { BrowserProvider } from "ethers";
const provider = new BrowserProvider((window as any).ethereum);
const signer = await provider.getSigner();
const caSDK = new CASDK(signer);

create

static async create(params: SDKCreationParams): Promise <CASDK>

Description

  • A factory method that creates an instance of the CASDK class. It accepts parameters as defined by SDKCreationParams, including the Signer object, and returns a new instance of the CASDK class.

Parameters

parameterrequired/optionaltypedescription
SDKCreationParamsrequiredParameters as defined by SDKCreationParams, including the Signer object.
signerrequiredSignerAn abstraction of an Ethereum Account (Externally Owned Account-EOA), which can be used to sign messages and transactions.
casURLoptionalstringThe endpoint of Shib Central Authentication Service.

Example:

  import { BrowserProvider } from "ethers";
const provider = new BrowserProvider((window as any).ethereum);
const signer = await provider.getSigner();
const caSDK = await CASDK.create({
signer,
});

Returns

  • The above method returns an instance of the CASDK.

getRequestId

private async getRequestId(signer: Signer, chainId: string, AAaddress?: string): Promise<string>

Description

  • A private method that retrieves a request ID from CAS's API for a given Signer and chainId. This is used in the authentication process.
  • This method is not callable from the class instance, hence, no example provided for it.

Parameters

parameterrequired/optionaltypedescription
signerrequiredSignerAn abstraction of an Ethereum Account (Externally Owned Account-EOA), which can be used to sign messages and transactions.
chainIdrequiredstringBlockchain ID.
AAaddressoptionalstringAccount Abstraction address. If not provided, the address is retrieved from signer, if provided AAadress is used to get the request ID.

Returns: Promise<string>

authenticate

public async authenticate(AAaddress?: string): Promise<string>

Description

  • Authenticates the user by signing a message with their Signer, sending it to CAS's API, validating the response, and returning a JWT token if successful.

Parameters

parameterrequired/optionaltypedescription
AAaddressoptionalstringAccount Abstraction address. If not provided, the address is retrieved from signer, if provided AAadress is used to get the request ID.

Returns: Promise<string>

  • The above method returns a JWT token, if the signer is DID registered else it returns 401

Example:

  import { BrowserProvider } from "ethers";
const provider = new BrowserProvider((window as any).ethereum);
const signer = await provider.getSigner();
const caSDK = await CASDK.create({
signer,
});
// It returns a JWT token, given the signer is DID registered
const jwtToken = await caSDK.authenticate();

decode

public async decode(token: string): Promise<DecodedToken>

Description

  • Decodes a provided JSON Web Token using the jsonwebtoken library's decode method.

Parameters

parameterrequired/optionaltypedescription
tokenrequiredstringJSON Web Token.

Returns

  • The above method returns the decoded token given a JWT

Example:

  import { BrowserProvider } from "ethers";
const provider = new BrowserProvider((window as any).ethereum);
const signer = await provider.getSigner();
const caSDK = await CASDK.create({
signer,
});
// It returns a JWT token, given the signer is DID registered
const jwtToken = await caSDK.authenticate();
const decodedToken = await caSDK.decode(jwtToken);
console.log(`Decoded token is: ${JSON.stringify(decodedToken)}`)

verify

public async verify(token: string): Promise<DecodedToken>

Description

  • Verifies the validity of a provided JSON Web Token by fetching a JWKS endpoint from CAS, extracting the signing key, and verifying the token with this key.

Parameters

parameterrequired/optionaltypedescription
tokenrequiredstringJSON Web Token.

Returns

  • It returns the DecodedToken object.
  • The above method returns the verified token if token is valid else it returns status 500

Example:

  import { BrowserProvider } from "ethers";
const provider = new BrowserProvider((window as any).ethereum);
const signer = await provider.getSigner();
const caSDK = await CASDK.create({
signer,
});
// It returns a JWT token, given the signer is DID registered
const jwtToken = await caSDK.authenticate();
const verifiedToken = await caSDK.verify(jwtToken);
console.log(`Verified token is: ${JSON.stringify(verifiedToken)}`)

jwks

public async jwks(): Promise<JWKS>

Description

  • Returns the token signing keys which can be used for verifying the authenticity of the tokens.

Returns: Promise<JWKS>

Example:

  import { BrowserProvider } from "ethers";
const provider = new BrowserProvider((window as any).ethereum);
const signer = await provider.getSigner();
const caSDK = await CASDK.create({
signer,
});
const jwksKeys = await caSDK.jwks();

Error Handling

  • Authentication CASDK.authenticate() fails when signer does not have a valid DID
    • Error response: authenticate: Error: getRequestId: Signer does not have a valid DID

Configuration Options

  • Passing Signer object is mandatory for creating an instance of the SDK.