fix: allow http loopback issuers in the DPoP auth-code flow#18
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes local-development logins for the auth-code + PKCE + DPoP flow by relaxing oauth4webapi’s HTTPS-only enforcement only when the OIDC issuer is a loopback http: origin (e.g. http://localhost:3000), aligning behavior with the existing Bearer flow’s ability to permit insecure requests for dev.
Changes:
- Added an
isLoopbackHttpIssuerguard and threadedoauth.allowInsecureRequestsinto DPoP discovery, dynamic client registration, and token exchange calls. - Added
node:testregression tests for the loopback-issuer predicate. - Added an
npm testscript to run the new TypeScript tests via Node’s test runner.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/DPoPTokenProvider.ts |
Adds loopback HTTP issuer detection and passes allowInsecureRequests options through oauth4webapi calls for DPoP flow. |
test/DPoPTokenProvider.test.ts |
Adds regression tests validating when HTTP loopback issuers should/shouldn’t be allowed. |
package.json |
Adds a test script to run the new node:test suite. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const hostname = issuer.hostname.toLowerCase() | ||
| return ( | ||
| hostname === "localhost" || | ||
| hostname === "127.0.0.1" || | ||
| hostname === "[::1]" || | ||
| hostname === "::1" || | ||
| hostname.endsWith(".localhost") || | ||
| hostname.startsWith("127.") | ||
| ) |
| for (const issuer of [ | ||
| "http://example.com", | ||
| "http://solidcommunity.net", | ||
| "http://10.0.0.1:3000", // private but not loopback | ||
| "http://192.168.1.1", | ||
| "http://notlocalhost.example", | ||
| ]) { |
There was a problem hiding this comment.
I have run into this annoyance myself several times when running CSS locally.
But I would much rather not get into the heuristic of determining what is loopback. Also I would rather not relax the draconian behavior of the underlying library.
How about another design where there is a global switch that library consumers can toggle to enable insecure requestsm like
class InsecureConfiguration {
private static allowed: boolean = false
/** @deprecated */
static allow(){
console.error("Insecure requests allowed for oauth4webapi")
this.allowed = true
}
static get requestOptions(): { [oauth.allowInsecureRequests]?: boolean } {
return {[oauth.allowInsecureRequests]: this.allowed}
}
}which could be used like
const tokenResponse = await oauth.authorizationCodeGrantRequest(
as,
client,
auth,
params,
callback,
verifier,
{
DPoP: dpop,
...InsecureConfiguration.requestOptions
}
)We leave it to the caller to make the decision, help them with deprecation warning and error in console, don't interfere with their security stance outside their knowledge and don't get into URI parsing.
| hostname === "[::1]" || | ||
| hostname === "::1" || | ||
| hostname.endsWith(".localhost") || | ||
| hostname.startsWith("127.") |
| hostname === "localhost" || | ||
| hostname === "127.0.0.1" || | ||
| hostname === "[::1]" || | ||
| hostname === "::1" || |
There was a problem hiding this comment.
I think URL normalizes these to the above.
| const hostname = issuer.hostname.toLowerCase() | ||
| return ( | ||
| hostname === "localhost" || | ||
| hostname === "127.0.0.1" || |
There was a problem hiding this comment.
What about 127.0.0.2? (Since the block is 127.0.0.0/8.)
| * | ||
| * @see https://www.rfc-editor.org/rfc/rfc8252#section-7.3 (loopback redirect URIs) | ||
| */ | ||
| export function isLoopbackHttpIssuer(issuer: URL): boolean { |
There was a problem hiding this comment.
Private utility should not be exported.
| /** | ||
| * Whether an issuer is a loopback (localhost / 127.0.0.0/8 / ::1) `http:` origin. | ||
| * | ||
| * oauth4webapi v3 enforces HTTPS on every request by default; for a local dev | ||
| * Solid server (e.g. a Community Solid Server on `http://localhost:3000`) every | ||
| * OIDC HTTP call (discovery, dynamic client registration, the token endpoint) | ||
| * would throw `only requests to HTTPS are allowed`. We relax that enforcement | ||
| * for — and only for — loopback `http:` issuers, leaving HTTPS strictly required | ||
| * for every other (production) issuer. | ||
| * | ||
| * @see https://www.rfc-editor.org/rfc/rfc8252#section-7.3 (loopback redirect URIs) | ||
| */ |
There was a problem hiding this comment.
This is a private utility method. I'm not sure it should be documented like this.
The auth-code + PKCE + DPoP login flow (DPoPTokenProvider) failed against a loopback / localhost dev issuer such as a local Community Solid Server on http://localhost:3000. oauth4webapi v3 enforces HTTPS on every request by default, so discoveryRequest / dynamicClientRegistrationRequest / authorizationCodeGrantRequest all threw "only requests to HTTPS are allowed" on the http: issuer — blocking create-solid-app's "S2" auth-code login and other local-dev integration apps. BearerTokenProvider already threaded oauth.allowInsecureRequests through every call; DPoPTokenProvider did not. Relax HTTPS enforcement for — and only for — loopback http: issuers (localhost, *.localhost, 127.0.0.0/8, ::1) via a new isLoopbackHttpIssuer guard, leaving HTTPS strictly required for every production issuer. Add a node:test regression test for the predicate and a `test` npm script. Model: claude-opus-4-8 Provenance: Opus 4.8 (Fable unavailable) — re-review/upgrade candidate Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64c86a7 to
fa03656
Compare
|
Rebased 🤖 PSS agent — @jeswr's agent for prod-solid-server / the Solid app+Pod-Manager suite |
🤖 PSS agent (Claude Opus 4.8 — @jeswr's
prod-solid-server/ Solid app+Pod-Manager-suite agent).The auth-code + PKCE + DPoP login flow (
DPoPTokenProvider) fails against a loopback/localhost dev issuer such as a local Community Solid Server onhttp://localhost:3000.oauth4webapiv3 enforces HTTPS on every request by default, sodiscoveryRequest/dynamicClientRegistrationRequest/authorizationCodeGrantRequestall throwonly requests to HTTPS are allowedon thehttp:issuer — blocking local-dev login (e.g.create-solid-app's auth-code "S2" step and other local-dev integration apps).BearerTokenProvideralready threadedoauth.allowInsecureRequeststhrough every call;DPoPTokenProviderdid not. This PR relaxes HTTPS enforcement for — and ONLY for — loopbackhttp:issuers (localhost,*.localhost,127.0.0.0/8,::1) via a newisLoopbackHttpIssuerguard, threadingoauth.allowInsecureRequeststhrough the three OIDC calls. HTTPS stays strictly required for every production (non-loopback) issuer.Adds a dependency-free
node:testregression test for the predicate + atestnpm script. Gate:tscclean, 3/3 tests pass,npm audit0 vulns. (Note: the newtestscript isn't yet wired into.github/workflows/ci.yml, which currently runs onlynpx tsc— maintainer may want to add it.)