check our Documentation 👉 platform.tawasal.ae
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 duringbuildAuthorization
.state
: The samestate
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
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
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
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
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
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.