Repository navigation
IndieAuth: support profile scope by discovering author metadata from the user's site #885
Description
Activity
Let’s allow providing this information in configuration, falling back to discovery, and leave
emailfor now because, as you say, it’s a different scope.We can capture the issue of caching values retrieved via external requests in another issue.
Good — configuration with discovery as the fallback, and
emailleft alone.The shape I'd implement, unless you'd rather it lived elsewhere:
"@indiekit/endpoint-auth": { profile: { name: "…", url: "…", photo: "…", }, },
Each key optional and independent: what is configured is used as-is, anything absent is filled in from the h-card discovered at
me. So configuring onlynamestill takes the photo from the site, and configuring nothing behaves exactly as the discovery-only version does. Nothing configured and no h-card found means the scope yields nothing, as today.I'll open the caching issue separately and link it here.
One loose end you didn't mention:
/userinfo. I'll leave it out of this PR to keep it reviewable, and follow up with it after — say the word if you'd rather have them together.Perfect. And let’s add support for
/userinfoas part of this feature.@paulrobertlloyd Happy to add it. To be sure I build what you have in mind, here's my reading and two decisions I'd rather you made.
What I'd add: the optional userinfo endpoint — a
GETtaking a bearer token issued withprofilescope, returning the same object as theprofileproperty in the token response, discovered through auserinfo_endpointkey in the metadata document. Following the existing pattern inendpoint-auththat's${mountPath}/userinfo—/auth/userinfoby default, alongside/auth/tokenand/auth/introspect— plus one line in the metadata controller. The spec mandates no particular path, so if you'd rather it sat elsewhere that's a one-line change.Why it pairs well with discovery, beyond completeness: because the profile is discovered from the user's h-card rather than configured, it can legitimately be absent at authorization time and appear later — someone adds an h-card to their homepage a week after signing in. With profile-in-the-token-response alone, a client is stuck with what it first got until the user re-authorizes. A userinfo endpoint lets it re-read. Less two features than the second making the first robust.
Two things I'd rather you decided:
-
Fresh or captured? Discovery runs on demand, so userinfo can re-fetch the h-card and return current data — which seems like the point of a separate endpoint. The alternative is returning whatever was captured when the token was issued. The spec says "identical to the
profileproperty" without settling which, and the difference is user-visible: change your photo, and one updates while the other doesn't. -
Same PR or a follow-up? The discovery work is four files; userinfo adds a controller, a route, a metadata key and tests. I'd lean toward one PR since they share
getProfileInformationand reviewing them together shows the whole feature — happy to split if you'd rather keep the diff small.
One thing I'd leave out: the spec allows
profileand/oremailscope on the token.emailstays unsupported as agreed, so userinfo returns the profile object only — flagging it so it doesn't read as an oversight.On errors, I'd return the profile object for a valid token carrying
profile, and an OAuth-style error for one that doesn't, rather than an empty object — so a client can distinguish "not granted" from "no h-card found". Say if you'd rather it degrade silently, as the token response does.-
Going ahead with the defaults unless you object:
/userinfore-discovers the h-card on each request, since a client comes to it precisely to read current data; and one PR, since the endpoint and the token response sharegetProfileInformation. Error for a token withoutprofilescope, profile object otherwise,emailuntouched. Configuration first, discovery as fallback, as agreed above.Going ahead with the defaults unless you object
Sounds good!
Reacted by Ricardo- added a commit that references this issue
on Sep 13, 2026 - added a commit that references this issue
on Oct 7, 2026
Background
scope.jscurrently declaresprofile: { supported: false }, and in #534 you explained why:I've been running the second of those in a fork for a few months, and it works well enough that I'd like to offer it upstream — but the shape is your call, so an issue before a PR.
What clients see today
Requesting
profilescope gets nothing back, which some clients surface as a warning. indiebookclub asks for it, and @aciccarello noticed the gap in #534. It also means the profile URL is the only thing an authentication-only flow can return, even when the user's own site publishes an h-card saying who they are.indielogin.com requests it too, which seems worth adding because of what that service is: the replacement for indieauth.com, which now carries a deprecation notice pointing at it. When it delegates to a user's own IndieAuth server it builds the authorization request with
'scope' => 'profile':So the scope is asked for by the service the ecosystem is being pointed towards, not only by older clients. Signing in through indielogin.com against an Indiekit endpoint running the fork described below works, with
profileamong the requested scopes — though to be precise about what that shows: indielogin.com uses the profile URL for identity, so a server that ignores the scope still completes the sign-in. What it demonstrates is a current, actively maintained client requestingprofileas a matter of course, rather than the scope being a legacy concern.What the implementation does
On the token and profile-URL responses, when
profileis among the granted scopes:meURL — the authenticated user's own site.microformats-parserand find the representative h-card, preferring one whoseu-urlmatches the profile URL.name,urlandphotounderprofile, per §5.3 Profile Information.Nothing is configured: the information comes from what the user already publishes. If there's no h-card, or the fetch fails, the response is unchanged — the scope simply yields nothing rather than failing the request.
It touches four files: a new
profile.js, the flag inscope.js, and the two controllers that build responses. The form-encoded token response needs the profile object flattened to JSON, sinceURLSearchParamswould otherwise stringify it as[object Object].Things worth deciding before a PR
name,url,photo. The specification also allowsemail, behind the separateemailscope — I left that alone since it's a different scope and a different privacy question.getClientInformationfetchesclient_iduncached too, that seemed consistent, but it is another outbound request per authorization./userinfo. IndieAuth endpoint: Redeemauthorization_codefor profile information #534 also mentioned adding that path for the same response. Not implemented; happy to include it if you'd like them together.The fetch reuses the
isFetchableOriginguard added in #877, since it's the same kind of outbound request to a URL that ultimately came from user input.Happy to open a PR against whichever shape you prefer, or to leave it if you'd rather this waited for the database work you mentioned.