Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

OAuthSonas: A Tiny OpenID Connect Provider for Playwright E2E Tests

If you've ever wired up end-to-end tests for an app that uses OpenID Connect, you've probably done one of these: stubbed the OIDC library entirely and shipped a fake /login route that sets a session cookie, pointed your test suite at a…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

If you've ever wired up end-to-end tests for an app that uses OpenID Connect, you've probably done one of these:




  • stubbed the OIDC library entirely and shipped a fake /login route that sets a session cookie,

  • pointed your test suite at a real Auth0 tenant and prayed the rate limits wouldn't bite,

  • or written a half-baked mock that returns a hardcoded JWT and silently broke every token-validation rule in your middleware.



All three are bad. The first one means your E2E tests never exercise the path your users actually take. The second is flaky, slow, and leaks credentials. The third gives you green tests over broken code.



At Optimi we run edge infrastructure for enterprise customers, and our dashboards authenticate against a real OIDC provider. We wanted E2E tests that hit the actual browser Authorization Code flow — discovery, PKCE, callback, JWKS validation, userinfo, logout — without standing up a full Auth0 tenant per CI run. So we built one and open-sourced it.




💡 Project link: github.com/optimiweb/oauthsonas







Meet oauthsonas



oauthsonas (that's "OAuth-sonas" — personas for OAuth) is a small, in-memory OpenID Connect provider for local development and integration tests. It implements:




  • A real browser Authorization Code flow with S256 PKCE

  • Rotating refresh tokens (with reuse detection)


  • RS256 JWT access and ID tokens


  • Discovery, JWKS, userinfo, and RP-initiated logout


  • Configurable personas — pick who you "are" with one click

  • Stable, versioned DOM selectors for browser automation



It is deliberately production-unsafe — no database, no real authentication, single-replica only, in-memory state lost on restart. That's the point. It's a drop-in stand-in for your real IdP when your real IdP is the wrong tool for the job.



A new 2048-bit RSA key is generated on every start and published at /.well-known/jwks.json. Your relying party validates tokens against that JWKS the same way it would against Auth0's — no special test mode, no shortcuts.






Why this is better than mocking



When you stub the OIDC library in your tests, you silently disconnect your test from the protocol your application actually runs. State preservation, PKCE verifier handling, aud/iss checking, JWKS rotation, refresh-token rotation — none of it gets exercised.



oauthsonas keeps all of that real. Your app speaks OIDC the same way it does in production; only the human at the keyboard is replaced by a persona picker.



That gives you:




  • Real discovery URL for your middleware to pull configuration from

  • Real JWKS for RS256 signature validation

  • Real callback handling — your /auth/callback route runs unchanged

  • Real refresh-token rotation with reuse detection

  • Real RP-initiated logout with id_token_hint

  • Reproducible RBAC personas (roles, org_id, memberships) as JWT claims






Step 1 — Run the provider



Install the Go version declared in go.mod and either run from a checkout or install the command directly:




go install github.com/optimiweb/oauthsonas/cmd/oauthsonas@latest






Then start it with the example config:




oauthsonas --config config.example.yaml






Or, if you prefer containers:




podman run --rm -p 127.0.0.1:8181:8181 \
-e OAUTHSONAS_ALLOW_NON_LOOPBACK=true \
ghcr.io/optimiweb/oauthsonas:latest






Verify it's alive:




curl http://127.0.0.1:8181/.well-known/openid-configuration









{
"issuer": "http://127.0.0.1:8181",
"authorization_endpoint": "http://127.0.0.1:8181/oauth2/auth",
"token_endpoint": "http://127.0.0.1:8181/oauth2/token",
"jwks_uri": "http://127.0.0.1:8181/.well-known/jwks.json",
"userinfo_endpoint": "http://127.0.0.1:8181/userinfo",
"end_session_endpoint": "http://127.0.0.1:8181/logout",
...
}







⚠️ The default issuer is exactly http://127.0.0.1:8181. Do not substitute localhost in your client config — OIDC issuer matching is exact.







Step 2 — Point your app at it



Whatever OIDC library your app uses, configure it with the equivalent of:




OIDC_AUTHORITY=http://127.0.0.1:8181
OIDC_CLIENT_ID=dashboard
OIDC_REDIRECT_URI=http://127.0.0.1:5173/auth/callback
OIDC_POST_LOGOUT_REDIRECT_URI=http://127.0.0.1:5173/
OIDC_SCOPE="openid profile email"
OIDC_AUDIENCE=https://api.example.test






The example client defined in config.example.yaml is public and requires S256 PKCE — so set your library up for a public client with a code verifier. No client secret to leak, no symmetric JWT to forge.



When a developer hits your login button, they'll be redirected to oauthsonas's authorization page, where they pick a configured persona — say Acme Administrator — and the redirect comes back to your callback with a normal authorization code. Your library then exchanges it and validates the RS256 tokens against the published JWKS, exactly as in production.






Step 3 — (Optional) Define the personas you test against



The default config.example.yaml ships platform staff, account managers, and per-customer admin/viewer personas — already a useful RBAC vocabulary. To add your own:




personas:
- id: delta-viewer
subject: oauthsonas|delta-viewer
email: [email protected]
name: Delta Viewer
organization_id: org_delta
roles: [customer-viewer]






Roles are emitted as a JWT claim (roles by default). oauthsonas never expands roles into permissions — that's your app's job, as it should be. You can rename claims via a top-level claims: block (useful for Auth0-style namespaced keys like https://example.com/roles), or enforce a role vocabulary with allowed_roles. Validate a config without starting the server:




oauthsonas --config config.yaml --check-config






For tests, prefer one persona per RBAC branch you care about: one platform admin, one org-scoped customer admin, one org-scoped viewer, one multi-org staff member. Same matrix you'd use in your test suite, just expressed as YAML.






Step 4 — Drive the flow from Playwright



This is the fun part. The persona picker exposes stable, versioned selectors specifically for browser automation:




























Element Selector
Persona form container form[data-testid="persona-form-<id>"]
CSRF hidden input input[data-testid="csrf-input"]
Interaction ID field input[data-testid="interaction-id-input"]
Submit button button[data-testid="persona-select-<id>"]


That's the contract. oauthsonas will not change it without a major version bump — see the handler godoc for the full versioned form contract on POST /oauth2/auth/select.



Here's a minimal Playwright login helper you can drop into a fixture. It assumes your app's login button lives at / and redirects to the IdP.




import { test as base, expect, type Page } from "@playwright/test";

const ISSUER = "http://127.0.0.1:8181";

export type Persona =
| "platform-admin"
| "operator"
| "account-manager-acme"
| "acme-admin"
| "acme-viewer"
| "bravo-admin";

export async function signInAs(page: Page, persona: Persona) {
// 1. Trigger the OIDC flow from your app. The app redirects to /oauth2/auth
// on oauthsonas, which renders the persona picker.
await page.goto("/");
await page.getByRole("button", { name: /sign in/i }).click();

// 2. We're now on the oauthsonas authorization page. Make sure we arrived.
await page.waitForURL(new RegExp(`^${ISSUER}/oauth2/auth\\??`));

// 3. Click the persona button — the form is pre-filled with CSRF + interaction_id.
await page
.locator(`button[data-testid="persona-select-${persona}"]`)
.click();

// 4. oauthsonas responds with a 302 to your /auth/callback, which exchanges
// the code and redirects back to your authenticated landing page.
await page.waitForURL(/\/(app|dashboard|home)$/);
}

export const test = base.extend<{ authedPage: Page }>({
authedPage: async ({ page }, use) => {
await signInAs(page, "acme-admin");
await use(page);
},
});

export { expect };






A test then becomes trivially readable:




import { test, expect } from "../fixtures/auth";

test("an org admin can invite a new member", async ({ authedPage: page }) => {
await page.goto("/app/settings/members");
await page.getByRole("button", { name: /invite member/i }).click();
await page.getByLabel("Email").fill("[email protected]");
await page.getByRole("button", { name: /send invite/i }).click();
await expect(page.getByText(/invitation sent/i)).toBeVisible();
});

test("a viewer cannot invite members", async ({ page }) => {
await signInAs(page, "acme-viewer");
await page.goto("/app/settings/members");
await expect(page.getByRole("button", { name: /invite member/i })).toBeHidden();
});






No mocks. No injected cookies. No bypassed middleware. Your token-validation code, your session middleware, your aud/iss checks — all run for real.






Step 5 — Clean up: logout



RP-initiated logout works too, because your test should be able to reset identity between steps:




export async function signOut(page: Page) {
// Your app's logout route should 302 to the IdP's end_session_endpoint
// with an id_token_hint and a post_logout_redirect_uri.
await page.goto("/auth/logout");
await page.waitForURL("/"); // back to the unauthenticated landing page
}






oauthsonas derives the client from the aud claim of the id_token_hint, so logout works with or without an explicit client_id — exactly mirroring Auth0 and Keycloak behavior.






A Playwright project setup that works



Bundle Playwright tests with oauthsonas started in globalSetup and torn down in globalTeardown. sketch:




// playwright.config.ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
globalSetup: require.resolve("./tests/global-setup"),
globalTeardown: require.resolve("./tests/global-teardown"),
use: {
baseURL: "http://127.0.0.1:5173",
trace: "retain-on-failure",
},
webServer: {
command: "npm run dev",
port: 5173,
reuseExistingServer: !process.env.CI,
},
});









// tests/global-setup.ts
import { spawn } from "node:child_process";

let child: ReturnType<typeof spawn>;

export default async function () {
child = spawn("oauthsonas", ["--config", "oauthsonas.yaml"], {
stdio: "inherit",
});

const ok = await waitFor("http://127.0.0.1:8181/readyz", 30_000);
if (!ok) throw new Error("oauthsonas did not become ready");

process.env.OAUTHSONAS_PID = String(child.pid);
}

async function waitFor(url: string, timeoutMs: number) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try {
const r = await fetch(url);
if (r.ok) return true;
} catch {}
await new Promise((r) => setTimeout(r, 200));
}
return false;
}









// tests/global-teardown.ts
export default async function () {
const pid = Number(process.env.OAUTHSONAS_PID);
if (pid) process.kill(pid, "SIGTERM");
}






In CI, use the published container image from GitHub Container Registry instead of installing Go:




docker pull ghcr.io/optimiweb/oauthsonas:latest
docker run -d --rm \
-e OAUTHSONAS_ALLOW_NON_LOOPBACK=true \
-p 127.0.0.1:8181:8181 \
ghcr.io/optimiweb/oauthsonas:latest






Keep the published port loopback-bound, even in CI. The OAUTHSONAS_ALLOW_NON_LOOPBACK=true env only acknowledges that the container itself binds 0.0.0.0 — Docker needs that for port-forwarding, but the host-side port should still be 127.0.0.1 only. There is no reason ever to expose oauthsonas to the internet.






What you actually want to test



With the real flow in place, here's a non-exhaustive list of regressions your E2E suite now catches that mocking would have hidden:





  • PKCE verifier mismatch — if your library mis-handles the verifier, the token exchange fails against oauthsonas the same way it would against Auth0.


  • JWKS rotation after server restart — restart the oauthsonas process mid-test and verify your client refetches keys (it should — the kid changes).


  • aud/iss validation — set OIDC_AUDIENCE wrong and watch your middleware reject the token.


  • Scope downscoping on refresh — request offline_access, then refresh with a narrower scope; oauthsonas correctly narrows the new grant.


  • Refresh-token reuse detection — replay a refresh token and observe the rejection.


  • RBAC matrix — same flow, different personas, one assertion per branch.


  • Logout — confirm id_token_hint-based RP-initiated logout redirects back to your post_logout_redirect_uri.



These are precisely the bugs that mock auth hides. None of them are exotic; all of them have bitten someone I know in production.






What oauthsonas is not



This is worth being explicit about, because the temptation to "just use it in dev" can lead you astray:





  • Not for production. In-memory state, no real authentication, no database. A restart invalidates every token and every interaction.


  • Single-replica only. All protocol state is process-local. Don't try to put it behind a load balancer — JWKS will mismatch and cookies will fail.


  • No password flow, no user provisioning, no Auth0 Management API. It's a test fixture, not a tiny Auth0 replacement.


  • No persistent identities. Personas are YAML-defined; you can't "sign up" a new user through it.



Read SECURITY.md in the repo before you hack on it — the design is intentionally unsafe for some classes of deployment.






Roadmap-ish



oauthsonas is intentionally minimal and we plan to keep it that way. The roadmap is removing rough edges, not adding features. Things you can expect to improve:




  • Better logging attributes for correlating Playwright runs with OIDC events

  • More ergonomic persona picker HTML for screen readers (already accessible, but we're picky)

  • Tighter CI story around the published container image



Things we will not accept as PRs: databases, password authentication, multi-replica state, user provisioning, a Management API. There's a wide market of real IdPs for that.






Try it, star it, break it



Fair: this is a small tool solving a narrow problem. If your E2E tests currently mock auth and you've been quietly unhappy about it, give oauthsonas a try:





If you find a protocol edge your real IdP handles and oauthsonas doesn't, that's a bug — please open an issue with a minimal reproducer. We want this to be the last test-IdP you ever have to write.




oauthsonas is built by the team at Optimi, where we orchestrate enterprise edge infrastructure (CDN, DNS, security, technical SEO/GEO) for premium brands. We open-source small, sharp tools when they solve a problem we'd otherwise have to keep solving — and we'd rather they lived in the community than on a PowerPoint slide.




Found this useful? Drop a ⭐ on the repo so other people find it too.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
Syntax validiert (0 Fehler)
title: Detect Exploitation - OAuthSonas: A Tiny OpenID Connect Provider for Playwright E2E Tests
id: 000812b0-d7d1-4742-866c-159cf17263d4
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-26
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-26"
        description = "YARA Signature for "
    strings:
        $str = "OAuthSonas: A Tiny OpenID Conn" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("OAuthSonas A Tiny OpenID Connect Provide")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*OAuthSonas A Tiny OpenID Connect Provide*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "OAuthSonas A Tiny OpenID Connect Provide"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich OAuthSonas: A Tiny OpenID Connect Provid.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten OAuthSonas: A Tiny OpenID Connect Provider for Playwright E2E Tests

Thematisch verwandte Begriffe: OAuthSonas, Tiny, OpenID, Connect · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-100539 | OpenClaw (npm package 'openclaw') before 2026.8.1 fails to revoke memor…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag