How to Run Your Own Decentralized Authentication Service Using AuthN

Modern application architectures are increasingly moving towards microservices and decentralization. Monolithic systems, including traditional authentication libraries, often don‘t align well with this distributed approach. As a developer, you‘re likely familiar with the challenges of making centralized auth systems work across loosely coupled services.

The industry is trending towards offloading authentication to managed services like Okta, Auth0, Amazon Cognito, and Azure AD B2C. On paper, this seems like an easy solution – let a dedicated provider handle the complexities of authentication. However, these services come with their own set of tradeoffs that are important to understand.

The Pitfalls of Managed Authentication Services

While using a managed authentication service can reduce development effort, it‘s not without downsides:

  1. Incomplete feature sets: Services like Amazon Cognito have limited Identity Provider support and lack cross-region replication. Customization options are often constrained.

  2. Unpredictable costs: Many providers have complex pricing models based on monthly active users. Costs can quickly balloon, especially if you need advanced features.

  3. Data breach risks: Even large providers like Okta are not immune to security incidents. In 2022, Okta suffered a data breach, highlighting that critical user data is still vulnerable even in a managed service.

  4. Vendor lock-in: Tight coupling to a particular authentication provider makes it difficult to switch later without significant refactoring. You‘re at the mercy of the provider for feature updates, pricing changes, and continued operation.

  5. Debugging challenges: When authentication issues arise, you have limited visibility into the internals of the managed service to diagnose problems. You‘re dependent on the provider‘s support.

So if managed services aren‘t always the answer, what‘s the alternative? Enter AuthN – an open source project that gives you full control over your authentication stack.

Introducing AuthN – Own Your Authentication

AuthN is an open source authentication server that provides the core building blocks for securing your applications. It offers:

  • Passwordless and traditional email/password login flows
  • Session management using JWT tokens
  • OAuth2 integration for social login
  • Two-factor authentication

With AuthN, you get a production-grade auth system without being an expert in security protocols. It encapsulates industry best practices for credential management, password hashing, and secure token generation.

However, AuthN is not a complete turnkey solution. You‘ll still need to build the user-facing pieces like:

  • Login and registration forms
  • Linking AuthN users to your application‘s user records
  • Transactional emails for verification and password resets
  • Administrative dashboard for user management

The upside is you have full control and flexibility over the user experience and integration with your backend services. Let‘s walk through the process of deploying AuthN and integrating it with a sample application.

Deploying AuthN with Docker Compose

We‘ll use Docker Compose to run AuthN and its dependencies (Postgres and Redis). Here‘s a step-by-step guide:

  1. Set up the project folder structure:
mkdir -p authn-server/db
touch authn-server/docker-compose.yml 
touch authn-server/db/Dockerfile
  1. Generate self-signed SSL certificates for Postgres:
openssl req -new -text -passout pass:abcd -subj /CN=localhost -out server.req -keyout privkey.pem
openssl rsa -in privkey.pem -passin pass:abcd -out server.key
openssl req -x509 -in server.req -text -key server.key -out server.crt
  1. Create the Postgres Dockerfile:
FROM postgres:14.1-alpine

WORKDIR /var/lib/postgresql
COPY server.key server.key
COPY server.crt server.crt

RUN chown postgres:postgres server.key
RUN chmod 600 server.key
  1. Configure the AuthN server in docker-compose.yml:
version: "3.8"

services:
  authn:
    image: keratin/authn-server:1.1.6
    ports:
      - "8765:8765"
    environment:
      PORT: 8765
      AUTHN_URL: http://localhost:8765
      APP_DOMAINS: localhost
      DATABASE_URL: postgres://user:pass@db:5432/authn
      REDIS_URL: redis://redis:6379/0
      SECRET_KEY_BASE: super-secret-key-base
      SMTP_SENDER: ‘"AuthN" <[email protected]>‘  
  1. Configure the Postgres and Redis containers:
  db:
    build: ./db
    environment:
      POSTGRES_DB: authn
      POSTGRES_USER: user  
      POSTGRES_PASSWORD: pass
    command: -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key
    volumes:
      - db_data:/var/lib/postgresql/data

  redis:
    image: redis:6.2-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data

volumes:
  db_data:
  redis_data:
  1. Run docker compose up to start AuthN and its dependencies. The AuthN server will be accessible at http://localhost:8765.

Integrating AuthN in Your Application

Now that AuthN is running, you can integrate it with your frontend and backend services. AuthN provides SDKs for popular languages and frameworks to simplify integration.

Here‘s an example of using the AuthN JavaScript SDK in a React login form:

import React, { useState } from ‘react‘;
import { AuthN } from ‘authn-js‘;

const authn = new AuthN({
  endpoint: ‘http://localhost:8765‘,
  clientApp: ‘myapp‘
});

function LoginForm() {
  const [username, setUsername] = useState(‘‘);
  const [password, setPassword] = useState(‘‘);

  async function handleSubmit(event) {
    event.preventDefault();
    try {
      const token = await authn.key.login({
        username: username,
        password: password
      });
      console.log(token);
    } catch (err) {
      console.error(err);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <input  
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Log In</button>
    </form>
  );
}

export default LoginForm;

On the backend, you‘ll need to validate the JWT token generated by AuthN on authenticated requests. Here‘s an example using the AuthN Go library in a Go API:

import "github.com/keratin/authn-go/v2"

var jwt = authn.NewJWT("http://localhost:8765")

func protectedHandler(w http.ResponseWriter, r *http.Request) {
  var claims authn.Token
  err := jwt.VerifyRequest(r, &claims)
  if err != nil {
    w.WriteHeader(401)
    return
  }

  // Get the user ID from the token claims
  userID := claims.Subject
  // Fetch the associated user record from your application‘s database
  user := findUser(userID)

  // Continue with authenticated request
}

Security Considerations

When self-hosting your authentication service, it‘s critical to follow security best practices:

  • Carefully manage and rotate secrets like API keys and JWT signing keys
  • Use SSL/TLS encryption for all AuthN traffic
  • Keep AuthN and its dependencies updated with the latest security patches
  • Implement rate limiting to prevent brute force attacks
  • Regularly audit and monitor access logs

While AuthN provides a robust foundation, remember that you are ultimately responsible for the security of your users‘ data when self-hosting.

Cost Analysis

One advantage of self-hosting AuthN is avoiding the variable and often unpredictable costs of managed services. With AuthN, your main costs are:

  • Server infrastructure to run AuthN and its dependencies
  • Storage for the Postgres database (to store user records) and Redis (for ephemeral session data)
  • Network traffic for AuthN API requests

These costs tend to be more stable and predictable than those of managed authentication services. Plus, you have the flexibility to optimize your deployment for cost vs. performance.

However, don‘t forget to factor in the ongoing maintenance and operational overhead of running AuthN yourself. This includes monitoring, backups, scaling, and security patching. Managed services offload much of this work, which can be worth the cost for some teams.

Conclusion

Decentralized authentication is an important piece of the microservices puzzle. While managed services offer convenience, they come with tradeoffs in cost, flexibility, and control.

AuthN provides a compelling open source alternative for teams wanting to own their authentication stack. It encapsulates security best practices in an easy-to-deploy package. However, it‘s not a complete solution. You‘ll still need to build user-facing elements and integrate AuthN with your applications.

The choice between self-hosted AuthN and a managed service depends on your team‘s specific needs and constraints. If you have the technical expertise and want full control over your authentication flow, AuthN is a solid foundation on which to build. But if your team has limited bandwidth and is willing to trade some flexibility for convenience, a managed service may be the better fit.

Whichever path you choose, understanding the tradeoffs and carefully evaluating your requirements is key to making the right decision for your application.

Similar Posts