or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

flow.mdidentity.mdindex.mdoauth.mdproviders.mdrouter.md
tile.json

flow.mddocs/

Flow Helpers

CORS-safe authentication flow utilities for popup-based login with postMessage communication, ensuring secure cross-origin authentication flows.

Capabilities

CORS Request Validation

Middleware for ensuring requests include the X-Requested-With header for CORS safety.

/**
 * Middleware that ensures X-Requested-With header is present for CORS safety
 * @param req - Express request object
 * @param res - Express response object
 * @param next - Express next function
 */
function ensuresXRequestedWith(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): void;

Usage Example:

import { ensuresXRequestedWith } from "@backstage/plugin-auth-backend";
import express from "express";

const app = express();

// Apply CORS safety middleware
app.use("/auth", ensuresXRequestedWith);

// Protected authentication endpoints
app.post("/auth/github/handler/frame", (req, res) => {
  // This endpoint is now protected against CSRF attacks
});

Post Message Response

Utility for sending authentication responses to parent windows via postMessage API.

/**
 * Posts authentication response to parent window using postMessage API
 * @param res - Express response object
 * @param appOrigin - Origin URL of the parent application
 * @param response - Authentication response data
 */
function postMessageResponse(
  res: express.Response,
  appOrigin: string,
  response: WebMessageResponse
): void;

Usage Example:

import { postMessageResponse } from "@backstage/plugin-auth-backend";

// In OAuth callback handler
app.post("/auth/github/handler/frame", async (req, res) => {
  try {
    // Process OAuth authentication
    const { profile, providerInfo } = await processOAuthCallback(req);
    
    // Send success response to parent window
    postMessageResponse(res, "https://backstage.example.com", {
      type: "authorization_response",
      response: {
        profile,
        providerInfo,
        backstageId: profile.email,
      },
    });
  } catch (error) {
    // Send error response to parent window
    postMessageResponse(res, "https://backstage.example.com", {
      type: "authorization_response",
      error: {
        name: "AuthenticationError",
        message: error.message,
      },
    });
  }
});

Authentication Flow Pattern

The typical authentication flow uses these helpers in combination:

import { 
  ensuresXRequestedWith, 
  postMessageResponse, 
  OAuthAdapter 
} from "@backstage/plugin-auth-backend";

// Authentication flow implementation
const authProvider = {
  async start(req: express.Request, res: express.Response) {
    // Redirect to OAuth provider
    const authUrl = buildAuthorizationUrl(req);
    res.redirect(authUrl);
  },

  async frameHandler(req: express.Request, res: express.Response) {
    try {
      // Process OAuth callback
      const result = await handleOAuthCallback(req);
      
      // Send success response to parent window
      postMessageResponse(res, getAppOrigin(req), {
        type: "authorization_response",
        response: result,
      });
    } catch (error) {
      // Send error response to parent window
      postMessageResponse(res, getAppOrigin(req), {
        type: "authorization_response",
        error: {
          name: error.name,
          message: error.message,
        },
      });
    }
  },
};

// Apply middleware and create routes
const router = express.Router();
router.use(ensuresXRequestedWith);
router.get("/start", authProvider.start);
router.post("/handler/frame", authProvider.frameHandler);

Types

Web Message Types

/**
 * Response type for post-message authentication responses
 */
interface WebMessageResponse {
  /** Message type identifier */
  type: string;
  /** Success response data (optional) */
  response?: {
    /** User profile information */
    profile: ProfileInfo;
    /** Provider-specific information */
    providerInfo?: any;
    /** Backstage identity ID */
    backstageId?: string;
    /** Identity token */
    idToken?: string;
  };
  /** Error information (optional) */
  error?: {
    /** Error name */
    name: string;
    /** Error message */
    message: string;
    /** Error stack trace (optional) */
    stack?: string;
  };
  /** Additional message data */
  [key: string]: any;
}

Flow Security Types

/**
 * CORS-safe request validation options
 */
interface CorsValidationOptions {
  /** Required X-Requested-With header value */
  requiredHeader?: string;
  /** Allowed origins for CORS requests */
  allowedOrigins?: string[];
  /** Custom validation function */
  validator?: (req: express.Request) => boolean;
}

Security Considerations

CSRF Protection

The ensuresXRequestedWith middleware provides CSRF protection by requiring the X-Requested-With header:

// Frontend authentication request
fetch("/auth/github/handler/frame", {
  method: "POST",
  headers: {
    "X-Requested-With": "XMLHttpRequest", // Required for CORS safety
    "Content-Type": "application/json",
  },
  body: JSON.stringify(authData),
});

Origin Validation

Always validate the origin when using postMessageResponse:

function getAppOrigin(req: express.Request): string {
  const referer = req.get("Referer");
  if (!referer) {
    throw new Error("Missing referer header");
  }
  
  const url = new URL(referer);
  const allowedOrigins = ["https://backstage.example.com"];
  
  if (!allowedOrigins.includes(url.origin)) {
    throw new Error(`Invalid origin: ${url.origin}`);
  }
  
  return url.origin;
}

Popup Communication

The authentication flow typically uses popup windows for OAuth flows:

// Frontend popup authentication
function authenticateWithPopup(providerId: string): Promise<AuthResult> {
  return new Promise((resolve, reject) => {
    const popup = window.open(
      `/auth/${providerId}/start`,
      "auth",
      "width=500,height=600,scrollbars=yes"
    );

    const messageListener = (event: MessageEvent) => {
      if (event.origin !== window.location.origin) {
        return; // Ignore messages from other origins
      }

      if (event.data.type === "authorization_response") {
        window.removeEventListener("message", messageListener);
        popup?.close();

        if (event.data.error) {
          reject(new Error(event.data.error.message));
        } else {
          resolve(event.data.response);
        }
      }
    };

    window.addEventListener("message", messageListener);

    // Handle popup blocked or closed
    const checkClosed = setInterval(() => {
      if (popup?.closed) {
        clearInterval(checkClosed);
        window.removeEventListener("message", messageListener);
        reject(new Error("Authentication cancelled"));
      }
    }, 1000);
  });
}