Skip to main content

2 posts tagged with "javascript"

View All Tags

Passkeys Authentication Is Here

Β· 6 min read


Authentication is a core part of building any app. Until now, developers on Juno have relied on third-party providers like Internet Identity and NFID. Today we're providing a new option: Passkeys.

This new authentication option is available to all developers using the latest Juno SDK and requires the most recent version of your Satellite containers. You can now enable Passkeys alongside existing providers, and the JavaScript SDK has been updated to make authentication APIs more consistent across sign-in, sign-out, and session management.


πŸ”‘ What Are Passkeys?​

Passkeys are a passwordless authentication method built into modern devices and browsers. They let users sign up and sign in using secure digital keys stored in iCloud Keychain, Google Password Manager, or directly in the browser with Face ID, Touch ID, or a simple device unlock instead of a password.

Under the hood, Passkeys rely on the WebAuthn standard and the web API that enables browsers and devices to create and use cryptographic credentials. Passkeys are essentially a user-friendly layer on top of WebAuthn.

When stored in a password manager like iCloud Keychain or Google Password Manager, passkeys sync across a user’s devices, making them more resilient, though this does require trusting the companies that provide those services. If stored only in the browser, however, they can be lost if the browser is reset or uninstalled.

The good news is that most modern platforms already encourage syncing passkeys across devices, which makes them convenient for everyday use, giving users a smooth and safe way to log into applications.


πŸ€” Choosing Between Providers​

Each authentication method has its strengths and weaknesses. Passkeys provide a familiar, device-native login experience with Face ID, Touch ID, or device unlock, relying on either the browser or a password manager for persistence. Internet Identity and NFID, on the other hand, offer privacy-preserving flows aligned with the Internet Computer, but they are less familiar to mainstream users and involve switching context into a separate window.

When in doubt… why not both?

In practice, many developers will probably combine Passkeys and Internet Identity side by side, as we do in the starter templates we provide.

Ultimately, the right choice depends on your audience and product goals, balancing usability, privacy, and ecosystem integration.


πŸš€ How to Use Passkeys​

Using the new Passkeys in your app should be straightforward with the latest JavaScript SDK.

To register a new user with a passkey, you call signUp with the webauthn option:

import { signUp } from "@junobuild/core";

await signUp({
webauthn: {}
});

For returning users, signIn works the same way, using the passkey they already created:

import { signIn } from "@junobuild/core";

await signIn({
webauthn: {}
});

As you can notice, unlike with existing third-party providers, using Passkeys requires a distinct sign-up and sign-in flow. This is because the WebAuthn standard is designed so that an app cannot know in advance whether the user has an existing passkey, and this is intentional for privacy reasons. Users must therefore explicitly follow either the sign-up or sign-in path.

It is also worth noting that during sign-up, the user will be asked to use their authenticator twice:

  • once to create the passkey on their device
  • and once again to sign the session that can be used to interact with your Satellite.

Given these multiple steps, we added an onProgress callback to the various flows. This allows you to hook into the progression and update your UI, for example to show a loading state or step indicators while the user completes the flow.

import { signUp } from "@junobuild/core";

await signUp({
webauthn: {
options: {
onProgress: ({ step, state }) => {
// You could update your UI here
console.log("Progress:", step, state);
}
}
}
});

πŸ› οΈ Updates to the SDK​

Alongside introducing Passkeys, we also took the opportunity to clean up and simplify the authentication APIs in the JavaScript SDK.


Mandatory provider in signIn​

important

This is a breaking change.

Previously, calling signIn() without arguments defaulted to Internet Identity. With the introduction of Passkeys, we decided to drop the default. From now on, you must explicitly specify which provider to use for each sign-in call. This makes the API more predictable and avoids hidden assumptions.

In earlier versions, providers could also be passed as class objects. To prevent inconsistencies and align with the variant pattern used across our tooling, providers (and their options) must now be passed through an object.

import { signIn } from "@junobuild/core";

// Internet Identity
await signIn({ internet_identity: {} });

// NFID
await signIn({ nfid: {} });

// Passkeys
await signIn({ webauthn: {} });

Page reload on signOut​

important

This is a breaking change.

By default, calling signOut will automatically reload the page (window.location.reload) after a successful logout. This is a common pattern in sign-out flows that ensures the application restarts from a clean state.

If you wish to opt out, the library still clears its internal state and authentication before the reload, and you can use the windowReload option set to false:

import { signOut } from "@junobuild/core";

await signOut({ windowReload: false });

authSubscribe renamed to onAuthStateChange​

To make the API more consistent with the industry standards, we introduced a new method called onAuthStateChange. It replaces authSubscribe, which is now marked as deprecated but will continue to work for the time being.

The behavior remains the same: you can reactively track when a user signs in or out, and unsubscribe when you no longer need updates.

import { onAuthStateChange } from "@junobuild/core";

const unsubscribe = onAuthStateChange((user) => {
console.log("User:", user);
});

// Later, stop listening
unsubscribe();

πŸ“š Learn More​

Passkeys are now available, alongside updates to the authentication JS APIs. With passwordless sign-up and sign-in built into modern devices, your users get a smoother experience.

Check out the updated documentation for details on:


πŸ‘€ What's Next​

Passkeys are available today for developers building with Satellite containers and the JavaScript SDK.

Next, we'll bring Passkey support directly into the Console UI, so new developers can register easily and you can too.

To infinity and beyond,
David


Stay connected with Juno by following us on X/Twitter.

Reach out on Discord or OpenChat for any questions.

⭐️⭐️⭐️ stars are also much appreciated: visit the GitHub repo and show your support!

Announcing Serverless Functions in TypeScript

Β· 5 min read


One of the goals with Juno has always been to make building decentralized, secure apps feel like something you're already used to. No weird mental models. No boilerplate-heavy magic. Just code that does what you expect, without touching infrastructure.

And with this release, we're taking another step in that direction: You can now write serverless functions in TypeScript.

If you're a JavaScript developer, you can define backend behavior right inside your container. It runs in a secure, isolated environment with access to the same hooks and assertions you'd use in a typical Juno Satellite.

No need to manage infrastructure. No need to deploy a separate service. Just write a function, and Juno takes care of the rest.

Cherry on top: the structure mirrors the Rust implementation, so everything from lifecycle to data handling feels consistent. Switching between the two, or migrating later, is smooth and intuitive.


✨ Why TypeScript?​

Rust is still the best choice for performance-heavy apps. That's not changing.

But let's be real: sometimes you just want to ship something quickly. Maybe it's a prototype. Maybe it's a feature you want to test in production. Or maybe you just want to stay in the JavaScript world because it's what you know best.

Now you can.

You get most of the same tools, like:

  • Hooks that react to document or asset events (onSetDoc, onDeleteAsset, etc.)
  • Assertions to validate operations (assertSetDoc, etc.)
  • Utility functions to handle documents, storage, and even call other canisters on ICP

The JavaScript runtime is intentionally lightweight. While it doesn't include full Node.js support, we're adding polyfills gradually based on real-world needs. Things like console.log, TextEncoder, Blob, and even Math.random β€” already covered.


πŸ” Designed for Interop​

The approach to writing serverless functions in Rust and TypeScript is aligned by design. That means if you outgrow your TS functions, migrating to Rust won't feel like starting from scratch. The APIs, structure, and flow all carry over.

Start scrappy, scale gracefully.


πŸ‘€ A Taste of It​

Here's how simple it is to react to a document being created or updated:

import { defineHook, type OnSetDoc } from "@junobuild/functions";

export const onSetDoc = defineHook<OnSetDoc>({
collections: ["posts"],
run: async (context) => {
const data = context.data.data.after.data;
console.log("New post created:", data);
}
});

And here's for example how you'd validate a document before accepting it β€” using Zod for a clean, readable schema:

import { z } from "zod";
import { defineAssert, type AssertSetDoc } from "@junobuild/functions";
import { decodeDocData } from "@junobuild/functions/sdk";

const postSchema = z.object({
title: z.string().min(5, "Title must be at least 5 characters long")
});

export const assertSetDoc = defineAssert<AssertSetDoc>({
collections: ["posts"],
assert: (context) => {
const data = decodeDocData(context.data.data.proposed.data);
postSchema.parse(data);
}
});

That's it. No APIs to expose, no infra to manage. Just code that runs where your data lives.


πŸ–₯️ Local-First Development​

Alongside TypeScript support, we've rethought the local development experience.

Instead of providing a partial local environment, the mindset shifted to mimicking production as closely as possible.

You still get a self-contained image with your Satellite, but now you also get the full Console UI included. That means you can manage and test your project locally just like you would on mainnet.

See this other blog post for all the details.


🧰 Zero Tooling, Just Code​

Here's the beautiful part: even though your serverless functions are written in TypeScript, they're bundled and embedded into a Satellite module that's still compiled in Rust behind the scenes.

But you don't need to install Rust. Or Cargo. Or ic-wasm. Or anything that feels complicated or overly specific.

All you need is Node.js and Docker. The container takes care of the rest: building, bundling, embedding metadata and gives you a ready-to-run Satellite that runs locally and is ready to deploy to production.

In short: just code your functions. The container does the heavy lifting.


πŸ“‘ Already in the Wild​

This isn’t just a feature announcement β€” serverless functions in TypeScript are already live and powering real functionality.

I used them to build the ICP-to-cycles swap on cycles.watch, including all the backend logic and assertions. The whole process was documented over a few livestreams, from setup to deployment.

If you're curious, the code is on GitHub, and there’s a playlist on YouTube if you want to follow along and see how it all came together.

A screenshot of cycles.watch that integrates with OISY and use serverless functions in TypeScript to swap ICP for cycles


▢️ Try It Out​

We've put together docs and guides to help you get started. If you're already using the Juno CLI, you're just one juno dev eject away from writing your first function or start fresh with npm create juno@latest.

To infinite and beyond,
David


Stay connected with Juno by following us on X/Twitter.

Reach out on Discord or OpenChat for any questions.

⭐️⭐️⭐️ stars are also much appreciated: visit the GitHub repo and show your support!