Skip to content

fix(endpoint-auth): accept authorization requests without response_type - #884

Open
rmdes wants to merge 2 commits into
getindiekit:mainfrom
rmdes:fix/authorization-response-type
Open

fix(endpoint-auth): accept authorization requests without response_type#884
rmdes wants to merge 2 commits into
getindiekit:mainfrom
rmdes:fix/authorization-response-type

Conversation

@rmdes

@rmdes rmdes commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Fixes #883.

Problem

authorization.js requires response_type to be present, then separately accepts either code or the deprecated id. That combination is inconsistent: id means "authenticate only, don't issue a token", and clients of that era commonly omit the parameter entirely, which is the older form of the same thing.

indieauth.com sends the omitted form, together with an empty scope:

/auth?me=…&scope&client_id=https%3A%2F%2Findieauth.com%2F&redirect_uri=…&state=…

That empty scope is exactly the case §5.2 describes:

If the client omits this value, the authorization server MUST NOT issue an access token for this authorization code. Only the user's profile URL may be returned without any scope requested.

So an Indiekit authorization endpoint rejects an authentication-only request from the reference implementation, and anyone using Indiekit for IndieAuth cannot sign in through indieauth.com.

Fix

A missing response_type is treated as id:

const responseType = request.query.response_type ?? "id";
if (!/^(code|id)$/.test(String(responseType))) {  }

Nothing downstream branches on the value — whether an access token is issued depends on the requested scope — and an unrecognised value such as token is still rejected.

This changes behaviour you tested on purpose

200-authorization-no-response-type.js asserted that a missing response_type produces Missing parameter: response_type, so this was a deliberate strictness rather than an oversight. I've rewritten that test to assert the request is accepted and renamed it 302-… for the status it now expects — but if the strictness was intentional and you'd rather keep it, this should be closed rather than merged, and the incompatibility is worth documenting instead.

My reasoning for preferring compatibility: the endpoint already honours id when sent explicitly, so accepting its absence is consistent rather than newly permissive, and the practical cost of strictness is being locked out of the wiki.

Testing

  • 38/38 integration and 28/28 unit tests pass in endpoint-auth; eslint and prettier clean.
  • The rewritten test sends the same shape indieauth.com does, empty scope included, and asserts the redirect to consent.

Update: the client this affects is deprecated

indieauth.com now carries a deprecation notice — the service is being retired in favour of indielogin.com.

The replacement does not need this fix. When it delegates to a user's own IndieAuth server it sends the parameters: response_type=code is set by indieauth-client-php (src/IndieAuth/Client.php:435), and grant_type=authorization_code together with code_verifier at app/Provider/IndieAuth.php:73.

So this is compatibility with a service on its way out, and worth weighing on that basis. Against that: indieauth.com is still serving, and people still sign in through it — confirmed against a live Indiekit deployment today — so the breakage is real until the shutdown happens.

I also wrote above that indieauth.com is what the IndieWeb wiki authenticates with. I went to verify that and could not: I found no reference to either service on indieweb.org's login or front page. I should not have asserted it, so treat that claim as withdrawn — it does not affect the rest, which is about what indieauth.com sends.

@rmdes

rmdes commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

screenshot of the error :
image

@rmdes

rmdes commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Confirmed against a live Indiekit deployment: signing in through indieauth.com
now completes, returning "You've successfully authenticated as
https://rmendes.net/".

Being precise about what that evidences, since the deployment runs a fork: the
operative line here — const responseType = request.query.response_type ?? "id";
— is identical in what was deployed, but the surrounding file is not
byte-identical to this branch, because the fork also carries profile-scope work
unrelated to this PR. So this is confirmation of the change, not of this branch
as a whole.

Worth noting the two fixes are sequential. This one lets the authorization
request through; the code exchange then fails separately because indieauth.com
also omits grant_type (#890, PR #891). A site applying only one of the two
still cannot complete a sign-in, which is why the earlier symptom looked like
authentication succeeding and then breaking at the last step.

@paulrobertlloyd

Copy link
Copy Markdown
Collaborator

I’m not opposed to this change, ultimately it feels like better UX and a more robust approach. I just want to check, is this behaviour compatible with the IndieAuth spec do you think?

@rmdes

rmdes commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

Honest answer: no, not strictly. The spec requires response_type and only documents code, so this knowingly accepts a request the spec would call incomplete. Worth being plain about that rather than dressing it up.

The trade-off is narrower than it looks, and it's time-limited.

indielogin.com — the replacement — doesn't need this change. Its authorization request is built by indieauth-client-php (Client.php:435), which sets response_type=code. It also sends grant_type and PKCE. Fully conforming.

The only client that needs this is indieauth.com, which now carries a deprecation notice pointing at indielogin.com. Its request is built at auth-web.rb:518 and sends me, scope, client_id, redirect_uri, state — no response_type.

So: accepting a non-conforming request, to support a service being retired, which the thing replacing it doesn't need. It's still serving today and people still sign in with it — I hit this myself — but the value has an end date, and if you'd rather not carry the leniency for that, closing this is reasonable.

If it does go in, one detail worth your view: the default is id, which the current spec doesn't mention — it's from the 2020 revision, where it meant authentication-only. It's inert either way: responseType is only used to validate itself and never read again, so whether a token is issued still depends on the requested scope. Defaulting to code instead changes nothing downstream and reads better against the current spec.

@paulrobertlloyd

paulrobertlloyd commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Perhaps the pragmatic and most explicit approach then is to only allow this behaviour for indieauth.com. That would mean Indiekit is spec-compliant, while providing support for a deprecated yet still widely-used service.

If this exception is commented and easily deletable, even better!

@rmdes
rmdes force-pushed the fix/authorization-response-type branch from 6ac8cde to a6a1aa5 Compare August 23, 2026 21:46
@rmdes

rmdes commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

@paulrobertlloyd Done both ways round — scoped to indieauth.com, and the exception is a single commented block.

response_type is a required parameter again, so any other client that omits it gets the missing-parameter error the spec asks for. The exception only supplies the pre-specification default when client_id is indieauth.com:

// Deprecated exception, delete this block and restore
// `request.query.response_type` below: indieauth.com omits `response_type`,
// the pre-specification form of an authentication-only request (`id`). Its
// replacement, indielogin.com, sends `response_type=code` and needs none of
// this. `client_id` is not yet known to be a URL, so it is checked before
// being compared.
const clientId = String(request.query.client_id);
const isDeprecatedClient =
  URL.canParse(clientId) && isSameOrigin(clientId, "https://indieauth.com");
const responseType =
  request.query.response_type ?? (isDeprecatedClient ? "id" : undefined);

Deleting that block and putting request.query.response_type back in the two places below it returns the endpoint to strict behaviour. Nothing else to unpick.

isSameOrigin is the existing @indiekit/util helper, so this compares origins rather than matching on the string — a trailing slash or a path still matches, a lookalike host does not.

One implementation detail worth your eye: the value is threaded through as a local rather than written back to request.query. The application sets Express's query parser to simple, which re-parses the string on every access, so an assignment there is discarded before it can be read.

Tests cover both sides — a request from any other client still reports the missing parameter, one from indieauth.com reaches the consent form.

CI here fails at Download localisations before it installs anything: this branch is on my fork, so LOCALAZY_READ_KEY isn't exposed to the run. Verified locally instead — npm run lint exits 0, and packages/endpoint-auth is 67/67.

rmdes added 2 commits August 25, 2026 21:03
Signing in through indieauth.com, which is how the IndieWeb wiki
authenticates people, failed against an Indiekit authorization endpoint
with "Missing parameter: response_type".

The controller required response_type to be present, then separately
accepted either code or the deprecated id. That combination is
inconsistent: id means authenticate only, and clients of that era commonly
omit the parameter entirely, which is the older form of the same thing.
indieauth.com sends the omitted form, alongside the empty scope that §5.2
describes as the case where only the user's profile URL is returned.

A missing value is now treated as id. Nothing downstream branches on it —
whether an access token is issued depends on the requested scope — and an
unrecognised value is still rejected.

This changes behaviour that 200-authorization-no-response-type.js asserted
deliberately, so that test is rewritten to assert the request is accepted
and renamed for the status it now expects.

Fixes getindiekit#883
A request without `response_type` is rejected again, as the
specification requires, except when `client_id` is indieauth.com — the
one widely-used client that omits it, and one carrying a deprecation
notice pointing at indielogin.com, which sends a conforming request.

The exception is a single commented block. Deleting it and restoring
`request.query.response_type` in the two places below returns the
endpoint to strict behaviour.

The value is threaded as a local rather than written back to
`request.query`: the application sets Express's query parser to
`simple`, which re-parses on every access, so an assignment there is
discarded before it can be read.

Tests cover both sides of the exception: a request from any other
client still reports the missing parameter, one from indieauth.com
redirects to the consent form.
@rmdes
rmdes force-pushed the fix/authorization-response-type branch from a6a1aa5 to 838f20c Compare August 25, 2026 19:04
@rmdes

rmdes commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

CI can't run on these — the branches are on my fork, so the Localazy step has no readKey and the job stops before npm test. Here are the numbers from running the full suite locally against each PR's merge ref, which is the commit CI would have built:

tests pass fail
main (35ee4bb5) 777 776 0
#884 (cc93b2fd) 778 777 0
#891 (d14c0830) 779 778 0
#893 (fbdea3da) 779 778 0

The one skipped test (Throws error deleting a file) is skipped on main too.

The order these want reading in, which the titles don't convey:

  1. fix(endpoint-auth): accept authorization requests without response_type #884 — lets indieauth.com's authorization request through, where response_type is omitted.
  2. fix(endpoint-auth): accept code exchange without grant_type at the authorization endpoint #891 — the code exchange that then fails, because grant_type is omitted too. A site applying only one of the two still can't complete a sign-in.
  3. fix(endpoint-auth): bind code exchange to the code’s own claims, not application state #893 — independent of those two: binds the code exchange to the code's own claims rather than application-wide state.

#891 and #893 both touch lib/middleware/code.js, so whichever merges second will need a rebase. #884 is in a different file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IndieAuth: authorization requests without response_type are rejected, breaking indieauth.com sign-in

2 participants