This is a preview of the Tawasal Platform provided to selected partners. Please direct discussions to your respective group chats.
SDKs
Node.js


check our Documentation 👉 platform.tawasal.ae


npm Bundle Size Bundle Size

This SDK provides functionalities to interact with the Tawasal SuperApp from a Node.js environment. The SDK allows you to extract user information, authorization tokens, and device tokens from cookies.

Installation

npm i @tawasal/node

Usage

OAuth

buildAuthorization(config: Configuration, redirectUri: string): Promise<{ state, code_challenge, code_verifier, nonce, redirectHref }>

Initiates the OAuth flow by generating the necessary authorization parameters.

  • Parameters:

    • config: The OAuth configuration object containing client metadata and server endpoints.
    • redirectUri: The URI where the user should be redirected after authorization.
  • Returns: An object containing:

    • state: Random string to maintain state between request and callback.
    • code_challenge: Challenge derived from the code verifier for PKCE.
    • code_verifier: Plaintext code verifier.
    • nonce: A unique string to mitigate replay attacks.
    • redirectHref: The full URL to redirect the user to initiate login.

grantAuthorizationCode(config: Configuration, url: string, verifier: string, state: string, nonce: string): Promise<{ access_token, sub }>

Exchanges the authorization code for an access token and subject identifier.

  • Parameters:

    • config: Tawasal OAuth configuration.
    • url: Full callback URL including the code query parameter.
    • verifier: Code verifier originally generated during buildAuthorization.
    • state: The same state string passed to the authorization URL.
    • nonce: Nonce to ensure the token is valid and prevent replay attacks.
  • Returns: An object with:

    • access_token: OAuth access token.
    • sub: The subject identifier for the user.

fetchUser(config: Configuration, token: string, sub: string): Promise<User>

Fetches the authenticated user’s profile using the access token.

  • Parameters:

    • config: Tawasal OAuth configuration.
    • token: Access token received via the token exchange.
    • sub: Subject identifier (user ID).
  • Returns: A User object containing decoded user information.


receiveConfig(server: string, clientId: string, metadata: ClientMetadata, secret: string): Promise<Configuration>

Fetches and constructs the configuration object needed for OAuth calls.

  • Parameters:

    • server: Tawasal server base URL.
    • clientId: The application’s client ID.
    • metadata: ClientMetadata object with endpoint info.
    • secret: The application’s client secret.
  • Returns: A Configuration object used for all SDK OAuth interactions.

Usage with Different Frameworks Oauth

Next js server action example

"use server";
import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import crypto from "crypto";
import {
  buildAuthorization,
  fetchUser,
  grantAuthorizationCode,
  receiveConfig,
  Configuration,
  ClientMetadata,
} from "@tawasal/node";
 
const publicKey = `-----BEGIN PUBLIC KEY-----
KEY_GOES_HERE
-----END PUBLIC KEY-----`;
 
const SECRET_KEY = new Uint8Array(
  crypto.createHash("sha256").update(publicKey).digest(),
);
 
async function encrypt(data: string): Promise<string> {
  const enc = new TextEncoder();
  const iv = crypto.getRandomValues(new Uint8Array(12));
 
  const key = await crypto.subtle.importKey(
    "raw",
    SECRET_KEY.buffer,
    { name: "AES-GCM" },
    false,
    ["encrypt"],
  );
 
  const encrypted = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    enc.encode(data),
  );
 
  const buffer = new Uint8Array([...iv, ...new Uint8Array(encrypted)]);
  return Buffer.from(buffer).toString("base64");
}
 
async function decrypt(encryptedBase64?: string): Promise<string> {
  if (!encryptedBase64) {
    return "";
  }
 
  const raw = Buffer.from(encryptedBase64, "base64");
  const iv = raw.slice(0, 12);
  const data = raw.slice(12);
 
  const key = await crypto.subtle.importKey(
    "raw",
    SECRET_KEY.buffer,
    { name: "AES-GCM" },
    false,
    ["decrypt"],
  );
 
  const decrypted = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv },
    key,
    data,
  );
 
  return new TextDecoder().decode(decrypted);
}
 
let clientId: string = "clientId";
let clientSecret: string = "clientSecret";
let server = "https://api.stage.tawasal.ae/";
 
const clientMetadata: ClientMetadata = {
  client_id: clientId,
  client_secret: clientSecret,
  issuer: "https://api.stage.tawasal.ae",
  authorization_endpoint: "https://api.stage.tawasal.ae/oauth2/v1/signin",
  token_endpoint: "https://api.stage.tawasal.ae/oauth2/v1/token",
  userinfo_endpoint: "https://api.stage.tawasal.ae/oauth2/v1/userinfo",
  revocation_endpoint: "https://api.stage.tawasal.ae/oauth2/v1/revoke",
  end_session_endpoint: "https://api.stage.tawasal.ae/oauth2/v1/endsession",
  jwks_uri: "https://api.stage.tawasal.ae/oauth2/v1/certs",
  response_types_supported: ["code"],
  response_modes_supported: ["form_post"],
  subject_types_supported: ["public"],
  id_token_signing_alg_values_supported: ["RS256"],
  token_endpoint_auth_methods_supported: ["client_secret_post"],
  claims_supported: ["aud", "email", "name", "family_name", "phone"],
  code_challenge_methods_supported: ["S256"],
  grant_types_supported: ["authorization_code", "refresh_token"],
  scopes_supported: ["openid", "profile", "email", "phone", "offline_access"],
};
 
export async function authFlow() {
  const headerStore = headers();
  const ua = headerStore.get("user-agent");
  const host = headerStore.get("referer") || "";
  const version = getTawasalVersion(ua!, "Tawasal");
 
  if (version) {
    const cookieStore = cookies();
    const state = await decrypt(cookieStore.get("state")?.value);
    const code_challenge = await decrypt(
      cookieStore.get("code_challenge")?.value,
    );
    const nonce = await decrypt(cookieStore.get("nonce")?.value);
    const code_verifier = await decrypt(
      cookieStore.get("code_verifier")?.value,
    );
 
    const token = await decrypt(cookieStore.get("access_token")?.value);
    const sub = await decrypt(cookieStore.get("sub")?.value);
    const user = cookieStore.get("user");
 
    console.log({
      state,
      code_challenge,
      nonce,
      code_verifier,
      token,
      sub,
      user,
    });
 
    const tokenFlow = async (config: Configuration) => {
      const cookieStore = cookies();
      // one eternity later, the user lands back on the redirect_uri
      // Authorization Code Grant
      console.log({ host });
      try {
        console.log({ code_verifier, nonce });
        const { access_token, sub } = await grantAuthorizationCode(
          config,
          host,
          code_verifier,
          state,
          nonce,
        );
 
        cookieStore.set("access_token", await encrypt(access_token));
        cookieStore.set("sub", await encrypt(sub));
      } catch (error) {
        console.error(error);
        await removeCookies();
        return await redirectFlow(config);
      }
    };
 
    const redirectFlow = async (config: Configuration) => {
      const cookieStore = cookies();
      let code_challenge_method = "S256";
 
      let { state, code_challenge, code_verifier, nonce, redirectHref } =
        await buildAuthorization(config, process.env.NEXT_PUBLIC_DOMAIN!);
      cookieStore.set("code_verifier", await encrypt(code_verifier));
      cookieStore.set("code_challenge", await encrypt(code_challenge));
      cookieStore.set("nonce", await encrypt(nonce));
      cookieStore.set("state", await encrypt(state));
 
      return redirect(redirectHref);
    };
 
    const getUser = async (config: Configuration) => {
      const cookieStore = cookies();
      const token = await decrypt(cookieStore.get("access_token")?.value);
      const sub = await decrypt(cookieStore.get("sub")?.value);
 
      const userInfo = await fetchUser(config, token, sub);
      console.log("UserInfo Response", userInfo);
      cookieStore.set(
        "user",
        Buffer.from(JSON.stringify(userInfo)).toString("base64"),
      );
    };
 
    const config = await receiveConfig(
      server,
      clientId,
      clientMetadata,
      clientSecret,
    );
 
    if ((!token || !sub) && !Array.from(new URL(host).searchParams).length) {
      return await redirectFlow(config);
    } else if (!token && !sub) {
      await tokenFlow(config);
    }
    await getUser(config);
 
    return true;
  }
 
  return true;
}
 
export async function removeCookies() {
  const cookieStore = cookies();
  cookieStore.delete("state");
  cookieStore.delete("code_challenge");
  cookieStore.delete("nonce");
  cookieStore.delete("code_verifier");
  cookieStore.delete("access_token");
  cookieStore.delete("sub");
  cookieStore.delete("user");
}

Hono.dev middleware example

import { setCookie, getCookie, deleteCookie } from "hono/cookie";
import crypto from "crypto";
import Tawasal from "@tawasal/node";
import { readCookie } from "@utils/cookie";
import { getDictionary } from "@utils/i18n";
import { createRoute } from "honox/factory";
import { PrismaClient } from "@prisma/client";
import type { Context, Env } from "hono/dist/types";
 
export function getRawCookie(c: Context) {
  return getCookie(c, "Tawasal");
}
 
const publicKey = `-----BEGIN PUBLIC KEY-----
KEY_GOES_HERE
-----END PUBLIC KEY-----`;
 
const SECRET_KEY = new Uint8Array(
  crypto.createHash("sha256").update(publicKey).digest(),
);
 
const encrypt = async (data: string): Promise<string> => {
  const enc = new TextEncoder();
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const key = await crypto.subtle.importKey(
    "raw",
    SECRET_KEY.buffer,
    { name: "AES-GCM" },
    false,
    ["encrypt"],
  );
  const encrypted = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    enc.encode(data),
  );
  return Buffer.from([...iv, ...new Uint8Array(encrypted)]).toString("base64");
};
 
const decrypt = async (input?: string): Promise<string> => {
  if (!input) return "";
  const raw = Buffer.from(input, "base64");
  const iv = raw.slice(0, 12);
  const data = raw.slice(12);
  const key = await crypto.subtle.importKey(
    "raw",
    SECRET_KEY.buffer,
    { name: "AES-GCM" },
    false,
    ["decrypt"],
  );
  const decrypted = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv },
    key,
    data,
  );
  return new TextDecoder().decode(decrypted);
};
 
const clientId = "clientId";
const clientSecret = "clientSecret";
const server = "https://api.stage.tawasal.ae/";
 
const clientMetadata: Tawasal.ClientMetadata = {
  client_id: clientId,
  client_secret: clientSecret,
  issuer: server,
  authorization_endpoint: `${server}oauth2/v1/signin`,
  token_endpoint: `${server}oauth2/v1/token`,
  userinfo_endpoint: `${server}oauth2/v1/userinfo`,
  revocation_endpoint: `${server}oauth2/v1/revoke`,
  end_session_endpoint: `${server}oauth2/v1/endsession`,
  jwks_uri: `${server}oauth2/v1/certs`,
  response_types_supported: ["code"],
  response_modes_supported: ["form_post"],
  subject_types_supported: ["public"],
  id_token_signing_alg_values_supported: ["RS256"],
  token_endpoint_auth_methods_supported: ["client_secret_post"],
  claims_supported: ["aud", "email", "name", "family_name", "phone"],
  code_challenge_methods_supported: ["S256"],
  grant_types_supported: ["authorization_code", "refresh_token"],
  scopes_supported: ["openid", "profile", "email", "phone", "offline_access"],
};
 
const prisma = new PrismaClient();
 
export default createRoute(async (c, next) => {
  if (c.req.method === "HEAD") {
    return c.newResponse(null, { status: 200 });
  }
 
  if (!c.get("prisma")) {
    c.set("prisma", prisma);
  }
 
  console.log("checking cookie");
  if (!c.get("cookie")) {
    console.log("moving to ouathMiddleware");
    const resp = await oauthMiddleware(c);
    if (resp) {
      return c.redirect(resp);
    }
    c.set("cookie", readCookie(c));
  }
 
  if (!c.get("dict")) {
    c.set("dict", getDictionary(c.get("cookie").locale));
  }
  await next();
});
 
const oauthMiddleware = async (c: Context<Env, any, {}>) => {
  const referer = new URL(c.req.url);
  const isOldUser = !!getRawCookie(c);
 
  if (isOldUser) return;
  let [state, code_challenge, code_verifier, nonce, token, sub] =
    await Promise.all([
      decrypt(getCookie(c, "state" + clientId)),
      decrypt(getCookie(c, "code_challenge" + clientId)),
      decrypt(getCookie(c, "code_verifier" + clientId)),
      decrypt(getCookie(c, "nonce" + clientId)),
      decrypt(getCookie(c, "access_token" + clientId)),
      decrypt(getCookie(c, "sub")),
    ]);
 
  const config: Tawasal.Configuration = await Tawasal.receiveConfig(
    server,
    clientId,
    clientMetadata,
    clientSecret,
  );
 
  const doRedirect = async () => {
    const auth = await Tawasal.buildAuthorization(
      config,
      process.env.NODE_PUBLIC_DOMAIN!,
    );
    setCookie(c, "state" + clientId, await encrypt(auth.state));
    setCookie(c, "code_verifier" + clientId, await encrypt(auth.code_verifier));
    setCookie(
      c,
      "code_challenge" + clientId,
      await encrypt(auth.code_challenge),
    );
    setCookie(c, "nonce" + clientId, await encrypt(auth.nonce));
    console.log("redirect to " + auth.redirectHref);
    return auth.redirectHref;
  };
 
  const doToken = async () => {
    console.log(
      `${process.env.NODE_PUBLIC_DOMAIN!}/${referer.searchParams.toString()}`,
    );
    try {
      const { access_token, sub: subData } =
        await Tawasal.grantAuthorizationCode(
          config,
          new URL(
            `${process.env.NODE_PUBLIC_DOMAIN!}/?${referer.searchParams.toString()}`,
          ).toString(),
          code_verifier,
          state,
          nonce,
        );
      console.log("received a token " + access_token);
      token = access_token;
      sub = subData;
      setCookie(c, "access_token" + clientId, await encrypt(access_token));
      setCookie(c, "sub", await encrypt(sub));
    } catch (err) {
      console.error(err);
      removeCookies(c);
      return await doRedirect();
    }
  };
 
  const fetchAndStoreUser = async () => {
    try {
      const userInfo = await Tawasal.fetchUser(config, token, sub);
      setCookie(
        c,
        "user",
        Buffer.from(JSON.stringify(userInfo)).toString("base64"),
      );
      console.log("fetched a user ", { userInfo });
    } catch (e) {
      console.error({ e });
      removeCookies(c);
      return await doRedirect();
    }
  };
 
  const hasCodeInURL = referer.searchParams.has("code");
 
  if ((!token || !sub) && !hasCodeInURL) return await doRedirect();
  if (!token || !sub) await doToken();
  return await fetchAndStoreUser();
};
 
export function removeCookies(c: Context<Env, any, {}>) {
  for (const key of [
    "state" + clientId,
    "code_challenge" + clientId,
    "nonce" + clientId,
    "code_verifier" + clientId,
    "access_token" + clientId,
    "sub",
    "user",
  ]) {
    deleteCookie(c, key);
  }
}
 

Cookie

getUser(cookie: object): User

This function extracts and decodes the user information from a provided cookie.

  • Parameters:
    • cookie: An raw string representing the cookie from which user information is to be extracted.
  • Returns: An object containing the user information.

Example:

import { getUser } from '@tawasal/node';
 
const user = getUser(req.cookies.get("tawasal"));
console.log(user);

getAuthorization(cookie: object)

This function generates an authorization token from the provided cookie.

  • Parameters:
    • cookie: An raw string representing the cookie from which the authorization token is to be extracted.
  • Returns: A base64 encoded string representing the authorization token, or null if the token is not available.

Example:

import { getAuthorization } from '@tawasal/node';
 
const authToken = getAuthorization(req.cookies.get("tawasal"));
console.log(authToken);

getDeviceToken(cookie: object)

This function extracts the device token from the provided cookie.

  • Parameters:
    • cookie: An raw string representing the cookie from which the device token is to be extracted.
  • Returns: A string representing the device token, or null if the token is not available.

Example:

import { getDeviceToken } from '@tawasal/node';
 
const deviceToken = getDeviceToken(req.cookies.get("tawasal"));
console.log(deviceToken);

checkSignature( userId: number, authKeyId: string, deviceToken: string, signatureBase64: string, publicKey: string)

This function verifies user.

  • Parameters:
    • userId: id of the tawasal user,
    • authKeyId: key of authorisation, second part of user token,
    • deviceToken: the token describing session on given device,
    • signatureBase64: first part od user token,
    • publicKey: the key that will be obtained in Dev Management
  • Returns: A boolean that says if session are legit.

Example:

import { getUser } from "@tawasal/node";
import { cookies } from "next/headers";
 
const store = cookies();
const cookie = store.get("tawasal");
const tawasal = getUser(cookie.value);
const tawasal = getTawasal();
const rawCookie = getRawCookie();
 
if (tawasal.userToken) {
    const [signature, auth_key_id, device_token, device_token_expires_at] =
        tawasal.userToken.split(":");
 
    const result = checkSignature(
        tawasal.userId,
        auth_key_id,
        device_token,
        signature,
        publicKey // obtain in Dev Management,
    );
    console.log(result) // true | false
}
 

Usage with Different Frameworks

Express.js

check out cookie-parser.

To use the SDK with an Express.js application:

// server.js or index.js
import express from 'express';
import cookieParser from 'cookie-parser';
import { getUser, getAuthorization, getDeviceToken } from '@tawasal/node';
 
const app = express();
app.use(cookieParser());
 
app.get('/user', (req, res) => {
  const tawasalCookie = req.cookies.tawasal;
  const user = getUser(tawasalCookie);
  res.json(user);
});
 
app.get('/auth', (req, res) => {
  const tawasalCookie = req.cookies.tawasal;
  const authToken = getAuthorization(tawasalCookie);
  res.send(authToken);
});
 
app.get('/device-token', (req, res) => {
  const tawasalCookie = req.cookies.tawasal;
  const deviceToken = getDeviceToken(tawasalCookie);
  res.send(deviceToken);
});
 
app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

Hono.dev

check out hono dev docs.

To use the SDK with Hono

hono/cookie + _middleware flow:

// name/app/routes/_middleware.ts)
import { getCookie } from "hono/cookie";
 
export default createRoute(async (c, next) => {
  if (!c.get("cookie")) {
    const rawCookie = getCookie(
        c, 
        "tawasal"
    )
    try {
      c.set("cookie", tawasal.getUser(rawCookie));
    } catch (e) {
      console.error(e);
    }
  }
})

Next.js

check out next js docs. To use the SDK with a Next.js

API route:

import { getUser, getAuthorization, getDeviceToken } from '@tawasal/node';
 
export default function handler(req, res) {
  const user = getUser(req.cookies.tawasal);
  const authToken = getAuthorization(req.cookies.tawasal);
  const deviceToken = getDeviceToken(req.cookies.tawasal);
 
  res.status(200).json({ user, authToken, deviceToken });
}

next/headers:

import { cookies } from "next/headers";
 
export default async function Page () {
  const store = cookies();
 
  return(
        <div>
          {JSON.stringify(getUser(store.get("tawasal").value))}
        </div>
    )
}

NestJS

check out nest js docs.

To use the SDK with a NestJS controller:

import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
import { getUser, getAuthorization, getDeviceToken } from '@tawasal/node';
 
@Controller('tawasal')
export class TawasalController {
  @Get('user')
  getUser(@Req() req: Request) {
    return getUser(req.cookies["tawasal"]);
  }
 
  @Get('auth')
  getAuthorization(@Req() req: Request) {
    return getAuthorization(req.cookies["tawasal"]);
  }
 
  @Get('device-token')
  getDeviceToken(@Req() req: Request) {
    return getDeviceToken(req.cookies["tawasal"]);
  }
}

Fastify

check out fastify cookie.

To use the SDK with a Fastify application:

import Fastify from 'fastify';
import cookie from '@fastify/cookie';
import { getUser, getAuthorization, getDeviceToken } from '@tawasal/node';
 
const fastify = Fastify();
fastify.register(cookie);
 
fastify.get('/user', (request, reply) => {
  const user = getUser(request.cookies.tawasal);
  reply.send(user);
});
 
fastify.get('/auth', (request, reply) => {
  const authToken = getAuthorization(request.cookies.tawasal);
  reply.send(authToken);
});
 
fastify.get('/device-token', (request, reply) => {
  const deviceToken = getDeviceToken(request.cookies.tawasal);
  reply.send(deviceToken);
});
 
fastify.listen(3000, (err, address) => {
  if (err) {
    console.error(err);
    process.exit(1);
  }
  console.log(`Server is running at ${address}`);
});

React Router

check out react router.

To use the SDK with a React Router application:

// src/router.js
import { createBrowserRouter } from 'react-router-dom';
import App from './App';
import UserPage from './pages/UserPage';
import { getUser, getAuthorization } from 'tawasal-superapp-sdk';
 
// Define loaders
async function userLoader({ request }) {
  const cookies = request.headers.get('Cookie');
  const cookieValue = cookies
          .split('; ')
          .find((row) => row
                  .startsWith('tawasal='))?.split('=')[1];
  const user = getUser(cookieValue);
  const authToken = getAuthorization(cookieValue);
  return { user, authToken };
}
 
const router = createBrowserRouter([
  {
    path: '/',
    element: <App />,
    children: [
      {
        path: 'user',
        element: <UserPage />,
        loader: userLoader,
      },
    ],
  },
]);
export default router;

License

Distributed under the MIT License. See LICENSE for more information.