Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Web Oauth

ASecurity

How to use @owlmeans/web-oauth — the browser half of OAuth sign-in: the consent, device-code and done screens, oauthEntrypoints, appendOAuthScreens, the useOAuthConsent hook, the suspend-then-dispatcher sign-in leg, the mandatory Tailwind @source line, the i18n resource and the data-testid contract. Auto-invoked when binding the OAuth screens in a web app, changing what the consent screen shows, diagnosing a person who is not returned to consent after signing in, or writing a test against the...

3 stars
0 votes
0 copies
0 views
Added 9/22/2026
developmentgoreactnodetestingapifrontendbackend

Works with

cliapimcp

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add owlmeans/common --skill web-oauth --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Web Oauth?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Web Oauth
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/owlmeans-web-oauth/badge)](https://www.skillsdirectory.com/skills/owlmeans-web-oauth)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: web-oauth
description: How to use @owlmeans/web-oauth — the browser half of OAuth sign-in: the consent, device-code and done screens, oauthEntrypoints, appendOAuthScreens, the useOAuthConsent hook, the suspend-then-dispatcher sign-in leg, the mandatory Tailwind @source line, the i18n resource and the data-testid contract. Auto-invoked when binding the OAuth screens in a web app, changing what the consent screen shows, diagnosing a person who is not returned to consent after signing in, or writing a test against these screens.
user-invocable: false
---

# @owlmeans/web-oauth

**Layer:** Web (React, shadcn + Tailwind v4)
**Install:** `"@owlmeans/web-oauth": "^0.1.18-rc.2"` in `dependencies`
**Contracts:** `@owlmeans/oauth` — aliases, `makeOAuthProtocols`, `oauthFlow`, `ConsentView`
**Server half:** `@owlmeans/server-oauth` (its `consentUrl`/`deviceUrl` must point at these screens)

## Key Exports

| Export | Description |
|--------|-------------|
| `oauthEntrypoints(opts?)` | `[bindScreen(consentScreen), bindScreen(deviceScreen), bindScreen(doneScreen)]` — spread into the app's entrypoint list. `opts` is `OAuthEntrypointOptions` (`parent?`, `path?`, `guard?`) |
| `appendOAuthScreens(ctx)` | Precondition check only: throws unless a `FLOW_STATE` resource is registered (call `appendFlowService` first). Returns the context |
| `OAuthConsentScreen` · `OAuthDeviceScreen` · `OAuthDoneScreen` | The screens (`RoutedComponent`s) |
| `useOAuthConsent(ref, aliases?)` | The headless logic: `{ stage, view, error, errorKind, approve, deny, switchAccount }` with `stage` in `checking | signing-in | loading | ready | deciding | done | error`; `errorKind` in `missing | not-found | expired | forbidden | failed` (`error` is the raw wire text, for a log — never rendered) |
| `OAUTH_I18N` (`'oauth'`) · `OAUTH_SUSPEND_TTL_MS` (10 min) | The library-tier i18n resource; how long a suspended sign-in stays valid |

The **consent API** (`load`/`approve`/`deny`) is bound by the *server* app, not here — the app binds
`loadConsent`/`approveConsent`/`denyConsent` from `@owlmeans/server-oauth` in its API context and
registers the same protocols in the web context's `bindAll` so `context.entrypoint(protocols.load)`
resolves. The three screens are `sticky`, parentless frontend routes (`/oauth/consent`,
`/oauth/device`, `/oauth/done`).

## Wiring

```tsx
import { appendOAuthScreens, oauthEntrypoints } from '@owlmeans/web-oauth'

appendFlowService(context)          // registers FLOW_STATE
appendOAuthScreens(context)
// entrypoints: ...oauthEntrypoints({ parent: account.base })  — the SAME opts the server passed
```

An app that builds its entrypoints from shared `managerProtocols` binds the three screens with
`bindScreen(protocols.consentScreen, handler(OAuthConsentScreen))` etc. — the components are
exported for exactly that. Translations register as a side effect of importing the package.

## The `@source` line — a consumer rule

Tailwind's scanner reads the CSS root plus `@source` directives and excludes `node_modules`, so a
class used only inside this package never reaches the app's stylesheet and the screens render
unstyled with nothing in the app's own sources to blame. Every consuming app adds:

```css
@source "../../../node_modules/@owlmeans/web-oauth/src";
```

Point at the shipped `src`, not `build`. Primitives (`button`, `card`, `input`, `label`) are private
under `src/@/` and imported only by relative specifiers — the consuming app's `@` alias is never part
of this package's runtime, and `tests/package-boundary.spec.ts` fails on any `from '@/…'`.

## The screens

- **Consent** — reads `?ref=` (a code grant's request id, or a device grant's user code; the server
  resolves which). Nothing is shown until the app is known to be authenticated; signed in, it calls
  `load`, shows the client's name and how it is known, the device name and `XXXX-XXXX` code, the
  redirect host (code grant), a localhost warning, and the scopes; **Approve** / **Deny**. Approve
  on a code grant does `window.location.href = redirect`; on a device grant it transits the flow to
  **Done** with `kind`/`ref` in the query. **It refuses to render when framed**
  (`window.top !== window.self`) — the approval click is exactly what a clickjacking overlay steals.
  **Use another account** (`oauth-consent-switch`) signs the session out (`auth.update(undefined)`) and
  goes through the same suspension, returning to this `ref`. Every failure is ONE translated sentence
  in `oauth-consent-error`, chosen by `errorKind` (`error-missing`, `error-not-found`, `error-expired`,
  `error-forbidden`, else the generic `error`) — the wire marker and stack are never shown. The kind is
  read from the marker in the message (`request-expired`, `request-not-found`, `forbidden`).
- **Device** — RFC 8628's `verification_uri`. Opened bare it shows an input for the code; opened
  with `?user_code=…` (`verification_uri_complete`) it goes straight on. Either way it runs the same
  `verify` → `next` → `consent` transition, so the two entries converge before anything else runs.
- **Done** — "you may close this window". The device variant says the connector will catch up on
  its own (`?kind=device`). Nothing to click; the MCP/CLI is polling.

## Signed out: suspend, dispatcher, come back

The consent screen never assumes a session. When `auth.authenticated()` is empty it builds
`makeFlowModel(oauthFlow)` (never the live flow slot), sets `ref`, transits the explicit `sign-in`
step, calls `suspendFlow(context, model, { expiresAt: now + OAUTH_SUSPEND_TTL_MS })` and sends the
browser to `DISPATCHER`. The chooser / Google / supervisor sign-in then lands through
`resumeSuspendedFlow` (the landing rule in `/login-plugins`, `/client-flow`) — back on the consent
screen with the **same `ref`**, now authenticated. What is stored is the destination entrypoint alias
plus the payload query, delete-on-read and expiry-checked, in `FLOW_STATE` (IndexedDB, so it survives
Google's full-page round trip). A plugin that still navigates to `HOME` instead of the landing breaks
this leg silently.

## `data-testid` contract

`oauth-consent-card`, `oauth-consent-client`, `oauth-consent-code`, `oauth-consent-approve`,
`oauth-consent-deny`, `oauth-consent-switch`, `oauth-consent-error`, `oauth-device-card`, `oauth-device-input`,
`oauth-device-submit`, `oauth-done-card`. Do not rename them — the end-to-end suites that drive
sign-in address these.

## i18n

Library-tier resource `oauth`, all seven languages (`en pl ru be uk es de`) under `src/i18n/`;
sections `consent`, `device`, `done`. `tests/i18n.spec.ts` fails when a language lacks a key English
has — add a key to all seven. An app overrides a string at its own tier; it does not fork a screen.

## Testing

Category D (`/testing-ui`) — `bun test ./tests` drives a real chromium against a real app, with only
the server absent:

- **Harness** (`tests/context.ts` boots Vite; `tests/harness/mount.tsx` is the app). It is the real
  `@owlmeans/web-client` context (auth service, IndexedDB resources, History-API router, the real
  dispatcher), the real flow service, `oauthEntrypoints()`, and the consent API bound with `bindAll`
  under a backend parent declared on the harness's own origin. The context stays un-initialized so the
  router compiles its routes.
- **Server stubs** are `page.route` answers on that origin (`tests/helpers.ts` `open(path, { stubs })`):
  `load`/`approve`/`deny` return a `ConsentView`, a `{ redirect }`, or a refusal in the wire shape
  `type|||message|||stack`. Nothing needs CORS and no live server is reached. `authenticate: true`
  also answers the dispatcher's token exchange.
- **A signed-in visit** is `?bearer=<makeBearer(USER)>`: the harness writes the record the auth
  service reads back (`idb-keyval` `auth:user`) BEFORE the app renders — services are not initialized
  yet, so `context.auth().update()` is not available at that point. `?lng=` picks the language.
  `window.__oauth.suspended()` / `.token()` read the real flow store and auth service back.
- **What is covered:** signed-out suspension (record, expiry, dispatcher, no request made); the full
  sign-in round trip back to the same `ref`; device and code grants (client, code, device, redirect
  host, the localhost warning, approve/deny each way, `location` follows the redirect); every failure as
  one sentence; the framed refusal (an iframe of `tests/harness/frame.html`, the framed app carrying its
  own `bearer`); use-another-account; the device screen bare and linked; the done screen; and the
  consent screen in all seven languages.
- Also `package-boundary.spec.ts` (no consumer aliases in source) and `i18n.spec.ts` (7-language parity).

A test of a refusal that the screen would otherwise hide still asserts what is ABSENT (no approve or
deny button, no wire text) — the point of those cases is that nothing can be approved.

Attribution

owlmeansowlmeans
View sourceMore from owlmeans →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284072 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2192 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

9881 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →