
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
oslo
This package is highly experimental - use at your own risk
A collection of utilities for auth, including:
oslo/cookie
: Cookie parsing and serializationoslo/crypto
: Light wrapper around Web Crypto API for generating hashes and signaturesoslo/encoding
: Encode base64, base64url, base32, hexoslo/oauth2
: OAuth2 helpers
oslo/oauth2/providers
: Built in OAuth2 providers (Apple, Github, Google)oslo/otp
: HOTP, TOTPoslo/password
: Password hashingoslo/random
: Generate cryptographically random strings and numbersoslo/token
: Verification tokens (email verification, password reset, etc)oslo/request
: CSRF protectionoslo/session
: Session managementoslo/webauthn
: Verify Web Authentication API attestations and assertionsAside from oslo/password
, every module works in any environment, including Node.js, Cloudflare Workers, Deno, and Bun.
npm i oslo
pnpm add oslo
yarn add oslo
oslo/cookie
import { serializeCookie } from "oslo/cookie";
const cookie = serializeCookie(name, value, {
// optional
expires: new Date(),
maxAge: 60 * 60,
path: "/",
httpOnly: true,
secure: true,
sameSite: "lax"
});
response.headers.set("Set-Cookie", cookie);
// names and values are URI component encoded
serializeCookie("this is fine =;", "this too =;");
import { parseCookieHeader } from "oslo/cookie";
// returns Map<string, string>
const cookies = parseCookieHeader("cookie1=hello; cookie2=bye");
oslo/crypto
import { sha1, sha256, sha384, sha512 } from "oslo/crypto";
const data = new TextEncoder().encode("Hello world");
const hash = sha1(data);
import { HMAC } from "oslo/crypto";
const hs256 = new HMAC("SHA-256");
const key = await hs256.generateKey();
const data = new TextEncoder().encode("Hello world");
const signature = await hs256.sign(key, data);
const validSignature = await hs256.verify(key, signature, data);
import { ECDSA } from "oslo/crypto";
const es256 = new ECDSA("SHA-256", "P-256");
const key = await es256.generateKey();
const data = new TextEncoder().encode("Hello world");
const signature = await es256.sign(key, data);
const validSignature = await es256.verify(key, signature, data);
import { RSASSAPKCS1v1_5 } from "oslo/crypto";
const rs256 = new RSASSAPKCS1v1_5("SHA-256");
const key = await rs256.generateKey();
const data = new TextEncoder().encode("Hello world");
const signature = await rs256.sign(key, data);
const validSignature = await rs256.verify(key, signature, data);
import { RSAPSS } from "oslo/crypto";
const rsaPSS256 = new RSAPSS("SHA-256");
const key = await rsaPSS256.generateKey();
const data = new TextEncoder().encode("Hello world");
const signature = await rsaPSS256.sign(key, data);
const validSignature = await rsaPSS256.verify(key, signature, data);
oslo/encoding
import { encodeBase64, decodeBase64 } from "oslo/encoding";
const encoded = encodeBase64(new TextEncoder().encode("hello world"));
const decoded = decodeBase64(encoded);
import { encodeBase64url, decodeBase64url } from "oslo/encoding";
import { encodeHex, decodeHex } from "oslo/encoding";
import { encodeBase32, decodeBase32 } from "oslo/encoding";
oslo/oauth2
import { OAuth2Controller } from "oslo/oauth2";
const authorizeEndpoint = "https://github.com/login/oauth/authorize";
const tokenEndpoint = "https://github.com/login/oauth/access_token";
const oauth2Controller = new OAuth2Controller(clientId, authorizeEndpoint, tokenEndpoint, {
// optional
redirectURI: "http://localhost:3000/login/callback"
});
import { generateState, generateCodeVerifier } from "oslo/oauth2";
const state = generateState();
const codeVerifier = generateCodeVerifier(); // for PKCE flow
const url = await createAuthorizationURL({
// optional
state,
scope: ["user:email"],
codeVerifier
});
import { verifyState, AccessTokenRequestError } from "oslo/oauth2";
if (!verifyState(storedState, state)) {
// error
}
// ...
try {
const { accessToken, refreshToken } = await oauth2Controller.validateAuthorizationCode<{
refreshToken: string;
}>(code, {
// optional
credentials: clientSecret,
authenticateWith: "request_body", // default: "http_basic_auth"
codeVerifier // for PKCE flow
});
} catch (e) {
if (e instanceof AccessTokenRequestError) {
// see https://www.rfc-editor.org/rfc/rfc6749#section-5.2
const { request, message, description } = e;
}
// unknown error
}
oslo/oauth2/providers
Some providers may require a code challenge and code verifier.
import { Github, Apple, Google } from "oslo/oauth2/providers";
const githubOAuth = new Github(clientId, clientSecret, {
scope: ["user:email"]
});
const url = await githubOAuth.createAuthorizationURL(state);
const tokens = await githubOAuth.validateAuthorizationCode(code);
oslo/oidc
import { parseIdToken } from "oslo/oidc";
const { sub, exp, email } = parseIdToken<{ email: string }>(idToken);
oslo/otp
import { generateHOTP } from "oslo/otp";
const secret = new Uint8Array(20);
crypto.getRandomValues(secret);
let counter = 0;
const otp = await generateHOTP(secret, counter); // default 6 digits
const otp = await generateHOTP(secret, counter, 8); // 8 digits (max)
import { TOTPController } from "oslo/otp";
import { TimeSpan } from "oslo";
const totpController = new TOTPController({
// optional
period: new TimeSpan(30, "s"), // default: 30s
digits: 6 // default: 6
});
const secret = new Uint8Array(20);
crypto.getRandomValues(secret);
const otp = await totpController.generate(secret);
const validOTP = await totpController.verify(otp, secret);
import { createHOTPKeyURI, createTOTPKeyURI } from "oslo/otp";
const secret = new Uint8Array(20);
crypto.getRandomValues(secret);
const issuer = "My website";
const accountName = "user@example.com";
const uri = createHOTPKeyURI(issuer, accountName, secret, {
//optional
counter: 0, // default: 0
algorithm: "SHA-1", // default: SHA-1
digits: 6 // default: 6
});
const uri = createTOTPKeyURI(issuer, accountName, secret, {
//optional
period: new TimeSpan(30, "s"), // default: 30s
algorithm: "SHA-1", // default: SHA-1
digits: 6 // default: 6
});
oslo/password
Hash passwords with argon2id, scrypt, and bcrypt using the fastest package available for Node.js.
import { Argon2id, Scrypt, Bcrypt } from "oslo/password";
// `Scrypt` and `Bcrypt` implement the same methods
const argon2id = new Argon2id(options);
const hash = await argon2id.hash(password);
const matches = await argon2id.verify(hash, password);
This specific module only works in Node.js. See these packages for other runtimes:
oslo/random
All functions are cryptographically secure.
import { generateRandomString, alphabet } from "oslo/random";
const id = generateRandomString(16, alphabet("0-9", "a-z", "A-Z")); // alphanumeric
const id = generateRandomString(16, alphabet("0-9", "a-z")); // alphanumeric (lowercase)
const id = generateRandomString(16, alphabet("0-9")); // numbers only
const id = generateRandomString(16, alphabet("0-9", "a-z", "A-Z", "-", "-")); // alphanumeric with `_` and`-`
import { random } from "oslo/random";
const num = random(); // cryptographically secure alternative to `Math.random()`
import { generateRandomNumber } from "oslo/random";
// random integer between 0 (inclusive) and 10 (exclusive)
const num = generateRandomNumber(0, 10);
oslo/request
CSRF protection.
import { verifyRequestOrigin } from "oslo/request";
// only allow same-origin requests
const validRequestOrigin = verifyRequestOrigin(request.headers.get("Origin"), {
host: request.headers.get("Host")
});
const validRequestOrigin = verifyRequestOrigin(request.headers.get("Origin"), {
host: request.url
});
if (!validRequestOrigin) {
// invalid request origin
return new Response(null, {
status: 400
});
}
// true
verifyRequestOrigin("https://example.com", "example.com");
// true
verifyRequestOrigin("https://foo.example.com", "bar.example.com", {
allowedSubdomains: "*" // wild card to allow any subdomains
});
// true
verifyRequestOrigin("https://foo.example.com", "bar.example.com", {
allowedSubdomains: ["foo"]
});
// true
verifyRequestOrigin("https://example.com", "foo.example.com", {
allowBaseDomain: true
});
oslo/session
import { SessionController } from "oslo/session";
import { generateRandomString, alphabet } from "oslo/random";
import { TimeSpan } from "oslo";
import type { Session } from "oslo/session";
// implements sliding window expiration
// expires in 30 days unless used within 15 days before expiration (1/2 of expiration)
// in which case the expiration gets pushed back another 30 days
const sessionController = new SessionController(new TimeSpan(30, "d"));
async function validateSession(sessionId: string): Promise<Session | null> {
const databaseSession = await db.getSession(sessionId);
if (!databaseSession) {
return null;
}
const session = sessionController.validateSessionState(sessionId, databaseSession.expires);
if (!session) {
await db.deleteSession(sessionId);
return null;
}
if (session.fresh) {
// session expiration was extended
await db.updateSession(session.sessionId, {
expires: session.expiresAt
});
}
return session;
}
async function createSession(): Promise<Session> {
const sessionId = generateRandomString(41, alphabet("a-z", "A-Z", "0-9"));
const session = sessionController.createSession(sessionId);
await db.insertSession({
// you can store any data you want :D
id: session.sessionId,
expires: session.expiresAt
});
return session;
}
const sessionCookieController = sessionController.sessionCookieController(cookieName, {
// optional
// if the cookie expires
expires: true, // default: true
// set to `false` in dev
secure: true, // default: false
path: "/", // default: "/"
domain, // default: undefined
sameSite // default: "lax"
});
const session = await createSession();
// store session
const cookie = sessionCookieController.createSessionCookie(session.sessionId);
// get cookie
const sessionId = sessionCookieController.parseCookieHeader(headers.get("Cookie"));
const sessionId = cookies.get(sessionCookieController.cookieName);
if (!sessionId) {
// 401
}
const session = await validateSession(sessionId);
if (session.fresh) {
// session expiration was extended
const cookie = sessionCookieController.createSessionCookie(session.sessionId);
// set cookie again
}
// delete session by overriding the current one
const cookie = sessionCookieController.createBlankSessionCookie();
oslo/token
For email verification tokens and password reset tokens.
import { VerificationTokenController } from "oslo/token";
import { isWithinExpirationDate } from "oslo";
import { generateRandomString, alphabet } from "oslo/random";
import type { Token } from "oslo/token";
// expires in 2 hours
const verificationTokenController = new VerificationTokenController(new TimeSpan(2, "h"));
async function generatePasswordResetToken(userId: string): Promise<Token> {
// check if an unused token already exists
const storedUserTokens = await db
.table("password_reset_token")
.where("user_id", "=", userId)
.getAll();
if (storedUserTokens.length > 0) {
// if exists, check the expiration
const reusableStoredToken = storedUserTokens.find((token) => {
// returns true if there's 1 hour left (1/2 of 2 hours)
return verificationTokenController.isTokenReusable(token.expires);
});
// reuse token if it exists
if (reusableStoredToken) return reusableStoredToken.id;
}
// generate a new token and store it
const token = verificationTokenController.createToken(
generateRandomString(63, alphabet("a-z", "0-9")),
userId
);
await db
.insertInto("password_reset_token")
.values({
id: token.value,
expires: token.expiresAt,
user_id: token.userId
})
.executeTakeFirst();
return token;
}
async function validatePasswordResetToken(token: string): Promise<string> {
const storedToken = await db.transaction().execute(async (trx) => {
// get token from db
const storedToken = await trx.table("password_reset_token").where("id", "=", token).get();
if (!storedToken) return null;
await trx.table("password_reset_token").where("id", "=", token).delete();
return storedToken;
});
if (!storedToken) throw new Error("Invalid token");
// check for expiration
if (!isWithinExpirationDate(storedToken.expires)) {
throw new Error("Expired token");
}
// return owner
return storedToken.user_id;
}
const token = await generatePasswordResetToken(session.userId);
// send password reset email with link (page with form for inputting new password)
await sendEmail(`http://localhost:3000/reset-password/${token.value}`);
oslo/webauthn
validateAttestationResponse()
does not validate attestation certificates. validateAssertionResponse()
currently only supports ECDSA using secp256k1 curve and SHA-256 (algorithm ID -7
).
import { WebAuthnController } from "oslo/webauthn";
const webauthn = new WebAuthnController("http://localhost:3000");
try {
const response: AttestationResponse = {
// all `ArrayBufferLike` type (`Uint8Array`, `ArrayBuffer` etc)
clientDataJSON,
authenticatorData
};
await webauthn.validateAttestationResponse(response, challenge);
} catch {
// failed to validate
}
try {
const response: AssertionResponse = {
// all `ArrayBufferLike` type (`Uint8Array`, `ArrayBuffer` etc)
clientDataJSON,
authenticatorData,
signature
};
await webauthn.validateAssertionResponse(
"ES256", // "RS256"
response,
publicKey, // `ArrayBufferLike`
challenge // `ArrayBufferLike`
);
} catch {
// failed to validate
}
FAQs
A collection of auth-related utilities
The npm package oslo receives a total of 40,795 weekly downloads. As such, oslo popularity was classified as popular.
We found that oslo demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.