Blog

We shipped our auth server to your browser with WASM. Here's how it's going

Picture this: you've built a powerful authorization system based on Google's Zanzibar design, capable of handling complex permission relationships at scale. Now you want to let developers try it out. How can you let them experiment freely without spinning up countless backend environments?


Picture this: you've built a powerful authorization system based on Google's Zanzibar design, capable of handling complex permission relationships at scale. Now you want to let developers try it out. How can you let them experiment freely without spinning up countless backend environments?

At WorkOS, this was our challenge with our Fine-Grained Authorization (FGA) product. We needed a playground where users could define their authorization schemas and test their "warrants" - access rules that specify relationships between the resources in your application (e.g. [store:A] is [parent] of [item:123] )

Traditional approaches would mean provisioning backend resources for each user who wanted to experiment, with no clear way to know how long to maintain their environment. Should we keep it running for the curious developer who spent 30 seconds poking around? What about the team lead who spent hours crafting their production schema?

Rather than tackle these resource management headaches, we took a different approach: what if we could deliver the auth server directly to the browser, allowing customers to run their own fully isolated playground environment?

Enter WebAssembly

WebAssembly (WASM) is a binary instruction format that allows you to run high-performance code in web browsers. It compiles languages like C, C++, Rust, or, in our case, Go into a format browsers can execute almost as fast as native machine code.

It's not JavaScript - it's a completely different beast that runs alongside JavaScript, designed for computationally intensive tasks that normally belong on a server.

When we say we shipped our auth server to the browser, we mean that we took our Go-based authorization service and compiled it to run as WASM. The command is deceptively simple:

GOOS=js GOARCH=wasm go build -o main.wasm

This tells Go to compile our code targeting JavaScript as the operating system and WebAssembly as the architecture. The command may seem simple, but getting everything working smoothly required solving some interesting challenges.

Some of WASM’s limitations forced us to choose alternative approaches that ultimately created a better user experience.

Why WASM for the playground (and not production)?

While shipping our auth server to the browser created an excellent playground experience, it's important to understand why this approach works specifically for the playground but isn't suitable for production FGA deployments.The playground's requirements align perfectly with WASM's strengths:

  • Individual, isolated environments for each user
  • No need for data synchronization between users
  • Short-lived sessions where performance and scalability aren't critical
  • Freedom to exclude complex production components

In contrast, our production FGA service handles challenges that would be impractical to address in a browser-based WASM environment:

  • Complex data consistency across distributed systems
  • High-performance caching layers
  • Multi-tenant data isolation
  • Real-time synchronization between multiple clients
  • Integration with existing authentication systems

We intentionally excluded our DynamoDB and caching layers from the WASM build, both to protect our IP and because these components wouldn't make sense in a browser context. Instead, we implemented a simplified storage layer using SQLite. It’s perfect for temporary, single-user experimentation but wouldn't scale to production needs.

The Power of Running Server-less

Moving our auth server to WASM opened up some fascinating possibilities. First, there's the immediate benefit of zero setup - no API keys, no configuration. Users load the playground and start experimenting. But the benefits go deeper than convenience.

Each user gets a completely isolated environment with a dedicated virtual database. This isolation isn't just about security—it means users can experiment freely without worrying about affecting others or cleaning up afterward.

Even more interesting, users can serialize and share their database state with teammates via stateful URLs, making collaboration on authorization models seamless.

The speed is impressive, too. The playground is remarkably snappy, with no network roundtrip needed for resource creation or auth checks. Every operation happens right in your browser, giving instant feedback that makes iteration cycles tight and productive.

And here's a neat side effect: once the WASM bundle is cached, the playground can run entirely offline. The same authorization engine that normally requires backend infrastructure runs entirely in your browser.

Technical Implementation: Working around the constraints

Getting a production server to run in the browser required some interesting technical adaptations. While compiling to WASM is straightforward, the browser environment imposes significant constraints that shaped our implementation:

Finding pure Golang implementations

One of our first challenges was finding dependencies that don't call into linked C libraries. We can't bring these along when compiling to WASM because Golang uses CGO to interface with C, which relies on system-level Application Binary Interfaces (ABIs) that aren't available in the WASM execution model.To address this, we had to:

  1. Identify pure Golang alternatives: We searched for libraries explicitly designed to avoid CGO dependencies. For example, libraries like x/crypto are written entirely in Go and work seamlessly with WASM builds.
  2. Evaluate performance trade-offs: While pure Golang libraries are WASM-compatible, they sometimes lack the performance optimizations of their CGO-based counterparts. In these cases, we either accepted the performance hit or looked for ways to optimize elsewhere in the application.
  3. Replace or rewrite functionality: Sometimes, suitable alternatives were unavailable, so we had to implement lightweight replacements ourselves. While this added to development time, it ensured compatibility with the WASM runtime and gave us fine-grained control over performance.

Reimagining file system access

WASM doesn't have direct file system access, so we had to reimagine how our service handles storage. We found a pure Go implementation of SQLite that could work with WASM's constraints, running in a virtual filesystem within the browser. This gives us the familiar database capabilities we need without requiring external services.

Taking a slight hit on multi-threading

Threading works differently, too. While Go's goroutines are technically supported in WASM, they're not as efficient as in native Go code because WASM lacks true multi-threading support.

Avoiding external network calls

The caching layer also needed reimagining. Instead of Redis, we implemented local browser-based caching. Network calls aren't straightforward in WASM either—while possible, they require special handling and introduce complexity. We found it cleaner to eliminate external calls entirely, leading to a more self-contained architecture.

Keeping an eye on the final binary size

Another challenge was the binary size. WASM modules can be quite large - often several megabytes - which impacts initial load times. We had to balance functionality against size, carefully considering which features were essential for the playground experience.

Bridging Go and JavaScript

While we implemented custom marshaling between Go and JavaScript, there are alternative approaches worth considering. For example, projects like go-wasm-http-server demonstrate how to run existing Go HTTP handlers directly in the browser using service workers. This approach could potentially eliminate the need for custom type marshaling and make the code more portable between browser and server environments.

This pattern is particularly interesting because it would allow seamless switching between local WASM execution and remote API calls, potentially opening up new possibilities for hybrid deployment models. However, we opted for a more direct integration for our initial playground implementation to maintain precise control over the browser-server interface.

Looking forward to future  playground experiences

We're excited about expanding the playground's capabilities. Plans are in motion to add visual elements for graphing authorization checks, making it even easier to understand and verify complex permission relationships. We're working to bring the FGA Playground experience into the WorkOS admin dashboard.

The bigger picture

This experiment in bringing our auth server to the browser demonstrates something interesting about modern web applications: with technologies like WASM, the boundary between client and server becomes more fluid, opening up new architectural possibilities when the use case calls for it.

For our authorization playground, WASM solved specific challenges around resource management and user isolation while creating an excellent developer experience. There's no infrastructure to manage, no multi-tenancy concerns, and the immediate feedback loop makes experimentation a joy.

However, it's important to recognize that this architecture was chosen specifically for the playground's unique requirements. Our production FGA service continues to run as a traditional backend service, handling the complex challenges of distributed systems, data consistency, and multi-tenant isolation that wouldn't be practical to address in a browser environment.

The code might be running in your browser, but it provides exactly the interface you'll use when integrating with our production API - we've just eliminated the network boundary for this specific use case. That's the real power of this approach: it lets us provide an authentic preview of our service in an environment optimized for experimentation.

In this article

This site uses cookies to improve your experience. Please accept the use of cookies on this site. You can review our cookie policy here and our privacy policy here. If you choose to refuse, functionality of this site will be limited.