Skip to main content

Custom Domains Support Upgrade

Β· 2 min read

Hey πŸ‘‹

A new release was shipped with two sweet improvements to the Console.

🌐 Custom Domains New API​

The hosting features for registering or administrating custom domains have been migrated to the API from the DFINITY Boundary Nodes team.

This migration makes managing domains:

  • More reliable
  • Significantly faster
  • Future-proof

Massive kudos to the team delivering this capability - awesome work πŸ’ͺ

✨ UI Improvements​

Alongside the domain upgrade, we shipped a few visual refinements:

Stronger contrast on cards and buttons for a cleaner, more readable interface (amazing what you can achieve by nudging brightness πŸ˜„)

The Launchpad has been slightly redesigned: "Create a new satellite" is now a primary button, bringing more clarity and guidance.

A screenshot of the launchpad that showcases the new contrast and actions that have been moved

Another screenshots from the authentication setup which displays the new tabs design

To infinity and beyond
David

Google Sign-In Comes to Juno

Β· 6 min read


TL;DR

You can now use your Google account to log into the Juno Console, and developers can add the same familiar login experience natively to the projects they are building.

Hey everyone πŸ‘‹

Today marks quite a milestone and I'm excited to share that Google Sign-In is now live across the entire Juno ecosystem.

From my perspective, though time will tell, this update has the potential to be a real game changer. It brings what users expect: a familiar, secure, and frictionless authentication flow.

It might sound a bit ironic at first - we're integrating Google, after all - but I'm genuinely happy to ship this feature without compromising on the core values: providing developers secure features and modern tools with a state-of-the-art developer experience, while empowering them with full control over a cloud-native serverless infrastructure.

Let's see how it all comes together.


πŸ’‘ Why It Matters​

Authentication is one of those things every product needs but, it's complex, it touches security, and it's easy to get wrong.

Until now on Juno, developers could use Internet Identity, which has its strengths but also its weaknesses. It provides an unfamiliar login flow - is it an authentication provider or a password manager? - and it's not a well-known product outside of its niche.

Passkeys were also added recently, but you only have to scroll through tech Twitter to see that for every person who loves them, there's another who absolutely hates them.

That's why bringing native Google Sign-In to Juno matters. Developers can now offer their users a familiar, frictionless login flow - and let's be honest, most people are used to this signing and don't care much about doing it differently.

At the same time, this doesn't mean giving up control. The authentication process happens entirely within your Satellite, using the OpenID Connect standard.

You can obviously combine multiple sign-in methods in one project, offering your users the choice that best fits their needs.

When it comes to Juno itself, this also matters for two reasons: it potentially makes onboarding - through the Console - more accessible for web developers who don't care about decentralization but do care about owning their infrastructure ("self-hosting"). And it opens the door to future integrations with other providers. I still hope one day to have a better GitHub integration, and this might be a step toward it.

Long story short, it might look like a trivial change - just a couple of functions and a bit of configuration - but it's another step toward Juno's long-term goal of making it possible to build and scale modern cloud products without compromising on what matters most: empowering developers and their users.


βš™οΈ How It Works​

When a user signs in with Google, Juno follows the OpenID Connect (OIDC) standard to keep everything secure and verifiable.

  1. The user signs in with Google.
  2. Google verifies their credentials and issues a signed OpenID Connect token.
  3. After redirecting to your app, that signed token (JWT) is sent to your Satellite.
  4. Inside the container, the token and its signature are verified, and the user's information (such as email or profile) is extracted.
  5. The Satellite then creates a secure session for the user.
  6. Once authenticated, the user can start interacting with your app built on top of your container's features.

🧩 Infrastructure​

At this point, you get the idea: aside from using Google as a third-party provider, there's no hidden β€œbig tech” backend behind this. Everything else happens within your Satellite.

The credentials you configure - your Google project and OAuth 2.0 Client ID - are yours. In comparison, those used in Internet Identity are owned by the DFINITY Foundation. So, this approach might feel less empowering for end users or more empowering for developers. You can see the glass half full or half empty here.

To validate tokens on the backend, your container needs access to the public keys Google uses to sign them. Since those keys rotate frequently, fetching them directly would introduce extra cost and resource overhead.

That's why the Observatory - a shared module owned by Juno (well, by me) - comes in. It caches and provides Google's public keys, keeping verification fast, efficient, and cost-effective.

Because Juno is modular, developers who want full control or higher redundancy can run their own Observatory instance. Reach out if you're interested.


πŸͺ„ Setup Overview​

Getting started only takes a short configuration. Once your Google project is set up, add your Client ID to your juno.config file:

import { defineConfig } from "@junobuild/config";

export default defineConfig({
satellite: {
ids: {
development: "<DEV_SATELLITE_ID>",
production: "<PROD_SATELLITE_ID>"
},
source: "dist",
authentication: {
google: {
clientId: "1234567890-abcde12345fghijklmno.apps.googleusercontent.com"
}
}
}
});

Then apply it using the CLI or manually through the Console UI. That's it, it's configured.


πŸ§‘β€πŸ’» Usage​

To add the sign-in to your app, it only takes a single function call - typically tied to a button like "Continue with Google".

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

await signIn({ google: {} });

For now, it uses the standard redirect flow, meaning users are sent to Google and then redirected back to your app.

You'll just need to handle that callback on the redirect route with:

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

await handleRedirectCallback();

I'll soon unleash support for FedCM (Federated Credential Management) as well.

Aside from that, nothing new - the rest works exactly the same.

Regardless of which authentication provider you're using, you can still track a user's authentication state through a simple callback:

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

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

And because type safety is the way, you can now safely access provider-specific data without writing endless if statements:

import { isWebAuthnUser, isGoogleUser } from "@junobuild/core";

if (isWebAuthnUser(user)) {
console.log(user.data.providerData.aaguid); // Safely typed βœ…
}

if (isGoogleUser(user)) {
console.log(user.data.providerData.email); // Safely typed βœ…
}

πŸ› οΈ Managing Users​

Once users start signing in, you can view and manage them directly in the Authentication section of the Console.

Each user entry displays key details such as:

  • Name and email address
  • Authentication provider
  • Profile picture (if available)

This view also lets you filter, sort, refresh or ban users etc.

Screenshot of the Juno Console showing users authenticated with Google


πŸ“š Learn More​

You can find all the details - including setup, configuration, and advanced options - in the documentation:

If you haven't tried Juno yet, head over to console.juno.build and sign in with Google to get started.

Ultimately, I can tell you stories, but nothing beats trying it yourself.

To infinity and beyond,
David


Reach out on Discord or OpenChat for any questions.

Stay connected with Juno on X/Twitter.

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

New (kind of) breadcrumbs nav, who dis?

Β· 2 min read

A screenshot of the new Console UI with navigation on the Datastore page

I tagged a new release as I deployed a new version of the Console UI to mainnet.

Aside from the updated navigation, which now displays the page title within breadcrumb-style navigation, and a few minor fixes, not much has changed feature-wise.

The biggest change in the frontend's codebase, which explains why so many files were touched, is a refactor to adopt the new pattern I’ve been using for DID declarations.

Instead of relying on auto-imported separate types, I now prefer grouping factories in a single module, exporting them from there, and importing the types through a suffixed module DID alias.

You can check out the pattern in Juno's frontend codebase or in the ic-client JS library. If you're curious about it, let me know.

It’s a small structural shift that makes the code much cleaner and more readable.

Finally, there are a few new E2E tests added in this repo and in the CLI.

To infinity and beyond 🍞✨
David

Offline Snapshots with the CLI

Β· One min read

Hi πŸ‘‹

Here is a small but handy update for your toolbox: you can now download and upload snapshots offline with the Juno CLI. 🧰

That means you can:

  • Keep a local copy of your module’s state
  • Stash it somewhere safe, just in case πŸ˜…
  • Restore it when needed
  • Or even move it between Satellites
juno snapshot download --target satellite
juno snapshot upload --target satellite --dir .snapshots/0x00000060101

Build & Run Scripts with β€œjuno run”

Β· 2 min read

Say hello to juno run πŸ‘‹

Build custom scripts that already know your env, profile & config.
Write them in JS/TS.
Run with the CLI. ⚑️

πŸ’ on top? Works out of the box in GitHub Actions!

For example:

import { defineRun } from "@junobuild/config";

export const onRun = defineRun(({ mode, profile }) => ({
run: async ({ satelliteId, identity }) => {
console.log("Running task with:", {
mode,
profile,
satelliteId: satelliteId.toText(),
whoami: identity.getPrincipal().toText()
});
}
}));

Run it with:

juno run --src ./my-task.ts

Now, let’s suppose you want to fetch a document from your Satellite’s Datastore (β€œfrom your canister’s little DB”) and export it to a file:

import { getDoc } from "@junobuild/core";
import { defineRun } from "@junobuild/config";
import { jsonReplacer } from "@dfinity/utils";
import { writeFile } from "node:fs/promises";

export const onRun = defineRun(({ mode }) => ({
run: async (context) => {
const key = mode === "staging" ? "123" : "456";

const doc = await getDoc({
collection: "demo",
key,
satellite: context
});

await writeFile("./mydoc.json", JSON.stringify(doc, jsonReplacer, 2));
}
}));

Fancy ✨

And since it’s TS/JS, you can obviously use any libraries to perform admin tasks as well.

import { defineRun } from "@junobuild/config";
import { IcrcLedgerCanister } from "@dfinity/ledger-icrc";
import { createAgent } from "@dfinity/utils";

export const onRun = defineRun(({ mode }) => ({
run: async ({ identity, container: host }) => {
if (mode !== "development") {
throw new Error("Only for fun!");
}

const agent = await createAgent({
identity,
host
});

const { metadata } = IcrcLedgerCanister.create({
agent,
canisterId: MY_LEDGER_CANISTER_ID
});

const data = await metadata({});

console.log(data);
}
}));

Coolio?

I’ll demo it next Monday in Juno Live. πŸŽ₯ https://youtube.com/@junobuild

Happy week-end β˜€οΈ

Labels & Quick Access in Console

Β· One min read

Hi πŸ‘‹

A new release is out β€” v0.0.56 πŸš€

This release focuses on frontend changes in the Console:

  • You can now label your Satellites with environment flags and tags. These appear in the launchpad, switcher, and overview. On the launchpad, they can also be used as search filters to quickly find the right Satellite.
  • A new navigation feature – called β€œSpotlight” or β€œQuick Access” – lets you jump anywhere in the Console or run actions (such as changing the theme) in seconds. Open it with the search icon in the navbar or by pressing Ctrl/Cmd + K.

Hopefully these make building with the Console a little more fun (and faster)!

Console label & quick access UI

Spotlight / Quick Access UI

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!

Faster Deploys & Precompression

Β· One min read

Hi πŸ‘‹

A new release is out β€” v0.0.54 πŸš€

Here are a few highlights:

⚑️ Faster deploys with proposals batching
πŸ“¦ Smarter precompression (optional Brotli + replace mode)
πŸ”€ Redirects fixed
✨ Shinier experience when deploying in your terminal

Checkout the release notes for details πŸ‘‰ Release v0.0.54 Β· junobuild/juno

Example of the new configuration option precompress:

export default defineConfig({
satellite: {
ids: {
production: "qsgjb-riaaa-aaaaa-aaaga-cai"
},
source: "dist",
precompress: {
algorithm: "brotli",
pattern: "**/*.+(css|js|mjs|html)",
mode: "replace"
}
}
});

Freezing, Gzip, & Console Enhancements

Β· 2 min read

Hi πŸ‘‹

A new release is out β€” v0.0.52 πŸš€

Here are a few highlights:
🧊 Longer default freezing thresholds
βœ… Gzipped HTML
πŸ” Allowed Callers
πŸ›  CLI Improvements
πŸ–₯ Polished Console UI

Checkout the release notes for details πŸ‘‰ Release v0.0.52 Β· junobuild/juno

Let me know if anything looks fishy β€” and happy coding! πŸ‘¨β€πŸ”§


πŸ–₯️ Two screenshots from the Console new features

The new β€œNotifications” component:

Notifications UI

The overall look: collapsible menu, redesigned tabs, more prominent actions, and more.

Console UI Update


As a side note on this release: aside from the custom domain feature, I think it’s now possible to configure your entire project β€” including authentication, data, storage, and emulator β€” directly within the Juno config file. Plus with type safety as the cherry on top.

This is especially handy for maintainability or if your project can be forked.

Here’s an example config that shows where and how the project is deployed, which headers to apply to assets, defines the structure of that data, and which image to use when running the emulator with Podman:

import { defineConfig } from "@junobuild/config";

/** @type {import('@junobuild/config').JunoConfig} */
export default defineConfig({
satellite: {
ids: {
development: "jx5yt-yyaaa-aaaal-abzbq-cai",
production: "fmkjf-bqaaa-aaaal-acpza-cai"
},
source: "build",
predeploy: ["npm run build"],
storage: {
headers: [
{
source: "**/*.png",
headers: [["Cache-Control", "max-age=2592000"]]
}
]
},
collections: {
datastore: [
{
collection: "notes",
read: "managed",
write: "managed",
memory: "stable"
}
]
}
},
emulator: {
runner: {
type: "podman"
},
satellite: {}
}
});

Documentation πŸ‘‰ https://juno.build/docs/reference/configuration