Skip to content

IndieAuth: support profile scope by discovering author metadata from the user's site #885

Description

@rmdes

Background

scope.js currently declares profile: { supported: false }, and in #534 you explained why:

Indiekit doesn't support profile scope as it has no information about the owner of the Indiekit server. There's a few options I'm considered:

  • Provide this information in Indiekit's configuration file
  • Perform website discovery when authenticating, and provide any author metadata found

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 profile scope 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':

// app/Provider/IndieAuth.php:17
$authorize = \IndieAuth\Client::buildAuthorizationURL($login_request['authorization_endpoint'], [
  'me' => $login_request['me'],
  'redirect_uri' => getenv('BASE_URL').'redirect/indieauth',
  'client_id' => getenv('BASE_URL').'id',
  'state' => $state,
  'code_verifier' => $code_verifier,
  '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 profile among 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 requesting profile as a matter of course, rather than the scope being a legacy concern.

What the implementation does

On the token and profile-URL responses, when profile is among the granted scopes:

  1. Fetch the me URL — the authenticated user's own site.
  2. Parse it with microformats-parser and find the representative h-card, preferring one whose u-url matches the profile URL.
  3. Return name, url and photo under profile, 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 in scope.js, and the two controllers that build responses. The form-encoded token response needs the profile object flattened to JSON, since URLSearchParams would otherwise stringify it as [object Object].

Things worth deciding before a PR

  • Discovery vs configuration. This implements discovery. If you'd rather have it in the configuration file, or support both with configuration winning, that changes the shape.
  • Which properties. I return name, url, photo. The specification also allows email, behind the separate email scope — I left that alone since it's a different scope and a different privacy question.
  • Fetching on every token request. Currently unguarded by any cache. Given getClientInformation fetches client_id uncached too, that seemed consistent, but it is another outbound request per authorization.
  • /userinfo. IndieAuth endpoint: Redeem authorization_code for 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 isFetchableOrigin guard 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions