Ticketing for IndieAuth
Ticketing for IndieAuth is an extension to IndieAuth that enables a publisher to send authorization, known as a ticket, that can be redeemed for an access token. This allows both solicited and unsolicited invitations to fetch restricted resources.
This extension was previously called Ticket Auth, but was renamed as it is an extension, not an Auth methodology in itself.
- This extension is still in early development.
- Latest Published Version
- discussion on GitHub
- Contributors: revision history
This IndieAuth extension enables a resource owner to offer access to a resource. Protected resources may include a resource server, application, API, etc.
The ticketing extension does not require the subject accessing the protected resource to expressly request access, but is instead offered it by the resource owner. It does this by allowing the resource owner to send a 'ticket' which can be redeemed by the subject for a token to access the protected resource.
The advantage of this approach is it allows for unique implementations as the method by which the resource determines who to send a ticket to is not defined by the extension, and therefore, the resource owner can, for example, opt to create a method for subjects to request a ticket or opt to send tickets based on other criteria.
Resource Owner- per OAuth 2.0 defined as an entity capable of granting access to a resource.
Resource- defined similarly to Resource Indicators for OAuth 2.0(RFC8707), but MUST be an https URL. Unlike RFC8707, in this extension, the target or resource is not being requested, it is being offered by the Resource Owner.
Subject- the IndieAuth User Profile URL that represents the user being offered access to the resource.
Ticket- a short lived random string that can be redeemed at the resource's token endpoint for a token to access the resource.
The Ticketing extension builds on IndieAuth by adding:
- A 'ticket' grant type for the token endpoint that allows the resource owner's ticket to be exchanged by the subject for a token.
- A ticket endpoint that is called by the protected resource to provide the ticket to the subject which will be redeemed at the resource owner's token endpoint.
- The thing that gets tokens only needs one new public endpoint, the endpoint that receives tickets
- Alice has choosen to share a protected resource, for example, a private post on her website, with Bob,
- Bob advertises a ticket endpoint on his website.
- Alice sends a ticket to Bob's ticket endpoint, which he can redeem for a token to access Alice's private post.
- Bob's ticket endpoint receives a ticket, which he exchanges for a token at Alice's token endpoint.
- Bob can now access Alice's private post.
Use-cases that inspired the development of this protocol:
- be able to fetch private posts from someone's feed
- Note: The most common use case thought of involves a private feed, but it does not inherently have to be a feed.
- don't rely on a "follow request" in order to do so
In brainstorming some use cases, the following scenarios came up for possible use cases building on integrating multiple existing pieces.
- Alice has a feed containing both public and private posts. By default, fetching it returns only the public posts. Alice decides she wants Bob to get the entire feed. Her site sends a ticket to Bob's ticket endpoint, which redeems it for a token that can access the feed.
- What does Bob do with the token? Assuming that Bob's ticket endpoint is coupled together with his Microsub endpoint, and he's already subscribed to her feed, it should automatically start fetching the feed with the token to get the enhanced version with the private posts. If Bob wasn't already following, it would still get the token, but it would send the feed to a moderation queue or a dedicated Microsub channel for Bob to decide how to handle it.
- If Alice has a single piece of restricted content she wants to share with Bob, it would work similarly to above, without the consideration of whether Bob was already privy to a different version of the content.
- In another scenario, Carol wants to share private posts, but not a specific private post. She can opt to create a form on her site that send tickets to anyone who authenticates to it using IndieAuth, and then subsequently decide internally what content each user represented by the token gets. This is effectively giving everyone a login into your site.
Deciding to Issue an Access Token
Alice has chosen to share private things with Bob. To do this, Alice needs to provide Bob with an access token.
This is in contrast to a mechanism of advertising the fact that there may be private/protected content at a URL by using something like a WWW-Authenticate header. By indicating there may be more to see at a URL, that itself is leaking information.
Why Alice chose to share could be because she decided to out of the blue, Bob notifying Alice that he is following her, or out of the blue without any affirmative action on Bob's part.
- How does Alice decide to send Bob a ticket? Not really important. It could be she monitors who is subscribed to her feed somehow, and wants to offer the full version. It could be Bob and Alice are friends and she invites him unsolicited.
- TODO: Expand on methods of Bob notifying Alice could be a follow post, or some other protocol, but is not related to authentication or required.
How a website decides to issue tokens does not need to be defined by this spec.
Aaron Parecki created an interface to be able to send tickets to arbitrary URLs, and shows the token info after the ticket has been redeemed.
Proposed mechanism for requesting a ticket
In order to request a ticket, a POST request would be made, indicating the subject is interested in accessing private content. How a website decides whether to provide a ticket in this case, and what resources it would unlock does not need to be defined by this spec.
In responding to a request, the endpoint would take the same approach as the webmention spec. Returning a 201, 202, or 400/500 for user and server errors respectively. The endpoint need not reveal whether it has chosen to issue a ticket, as the ticket endpoint would receive or not receive one as appropriate.
A ticket endpoint can be discovered by fetching the URL of the
subject and looking for the
rel=indieauth-metadata as described in IndieAuth Discovery.
The ticket sending mechanism fetches the metadata URL and finds the
ticket_endpoint property in the JSON body.
The ticket endpoint will need to discover the token endpoint to redeem the ticket at.
To expect that all pages that could be redeemed as a
resource would have IndieAuth Server Metadata (
rel=indieauth-metadata) or the older but still in use
rel=token_endpoint might reveal there is private information at that URL.
To address above, the ticket endpoint SHOULD use the provided iss parameter to find the metadata endpoint.
Discussion for alternatives is at https://github.com/indieweb/indieauth/issues/127
An older IndieAuth implementation may not yet support the indieauth-metadata endpoint or the issuer identifier, in which case:
- The ticket endpoint might choose to advertise using a
rel=ticket_endpointheader. However, being as the current version of IndieAuth requires the metadata endpoint, this is not recommended.
- The ticket sending mechanism may not send the issuer identifier for verification as they do not support the latest IndieAuth specification...in which case a ticket endpoint may choose to look for the token endpoint at the resource, but this is also not recommended.
Step 1: Generate the IndieAuth ticket
Alice generates a short-lived ticket. The ticket MUST expire shortly after it is issued to mitigate the risk of leaks, and MUST be valid for only one use. A maximum lifetime of 10 minutes is recommended.
The reason for short-lived is that you are sending something unsolicited to someone and it is safer to send something short-lived and single use, and require action to retrieve the actual token.
- Resource URL = https://alice.example.com/private/
- Ticket = 32985723984723985792834
- Deliver to ticket endpoint of the subject: https://bob.example.org/
Alice discovers the ticket endpoint of the subject. See #Discovery
Alice makes a POST request to the ticket endpoint. This is basically saying "if you wanted to fetch this as this person and access the resource, here's a ticket you can use to get an access token".
ticket- a random string that can be redeemed for an access token
resource- the access token will work at this URL(s). Multiple resources can be provided
subject- the access token should be used when acting on behalf of this URL
iss- The server's issuer identifier which is used to locate the metadata endpoint
POST https://auth.example.org/ticket Content-Type: application/x-www-form-urlencoded ticket=32985723984723985792834 &resource=https://alice.example.com/private/ &subject=https://bob.example.org &iss=https://https://alice.example.com/.well-known
When a ticket is sent, if there is no problem with the request, the ticket endpoint MUST return an HTTP 200 OK code or HTTP 202 Accepted if it is going to asynchronously make the token request.
Why not send the access token directly?
When you send a ticket, you are sending an unsolicited payload to the receiver. The ticket is not requested by the receiver, so you cannot guarantee they will be protecting it if they aren't expecting it. (It may be logged in intermediate proxy servers, written to log files on the server, etc.) If the receiver supports receiving tickets, then they will be explicitly requesting an access token by exchanging the ticket, so you can be more confident they will take measures to protect the access token. This way the access token is only delivered via an explicitly requested HTTPS connection to your server.
Additionally, given some IndieAuth servers don't support expiration of tokens(despite it being a recommendation), this could allow permanent access to protected data, which is not ideal.
Step 2: Redeem the ticket for an access token
Bob's ticket endpoint redeems the ticket for an access token. This token can support refresh tokens as per the IndieAuth specification.
The ticket endpoint makes a GET or HEAD request to discover the token endpoint,as described in Discovery by Clients, with the difference that instead of discovery at the user provided URL, discovery will use the issuer identifier,
The ticket endpoint SHOULD check the properties provided by the metadata endpoint. If the
grant_types_supported property is set, then ticket MUST be expressly noted or the endpoint should not proceed.
The ticket endpoint makes a POST request to the token endpoint to exchange the ticket for an access token. The POST request MUST contain the following parameters:
ticket- The ticket to be redeemed
And SHOULD support any optional parameters specified by the IndieAuth specification, such as client_id and scope, to allow a token to be issued with limitations.
The IndieAuth spec does not currently address the issuance of resource-limited tokens. The discussion for that is at https://github.com/indieweb/indieauth/issues/82. Regardless of this adoption, if a token endpoint wishes to support the ticket grant type, then it MUST either support the issuance of resource-limited tokens or support this by issuing a token with a scope reflecting same.
Should this also include client_id? https://github.com/indieweb/indieauth/issues/85
POST https://alice.example.com/token Content-type: application/x-www-form-urlencoded Accept: application/json grant_type=ticket &ticket=32985723984723985792834
Response is an access token as specified in the IndieAuth Spec.
Step 3: Use the access token
Now Bob's authorization endpoint has the remote access token that could be used to fetch Alice's feed.
How to get it to a client, such as a reader or the webmention endpoint verifier is up to those implementations, it may be internal, or may use another spec to coordinate.
- Would a token grant access to anything more specific than the provided resource, or would it be only for that specific resource? (e.g. should a token for
https://example.com/alice/also work on
- Moved discussion to https://github.com/indieweb/indieauth/issues/83
- Should it be assumed that the ticket endpoint will automatically redeem all tokens? It's technically an implementation detail, not a requirement of the spec, but witht he amount of time the ticket is valid, this seems a good move. Assuming the token endpoint supports revocation.
- The discussion indicates a few different use cases for granting tickets; it seems that some people want to generate tickets for a specific resource (e.g. a private post) sent securely to specific recipients, whereas other people want to generate tickets for being able to subscribe to a feed or otherwise retrieve data in a logged-in state. The original TicketAuth draft was to support the latter; do we also need to support the former? If
resourcepoints to specific private blog entries, that implies that it's up to the publisher to specifically send push notifications about all private content to all recipients, which has a lot of far-reaching implications.
- Would it make sense to have
resourcefor specific items,
realmfor blanket grants for an entire website/URL prefix/etc., and
issuerfor where to look for the token endpoint as an override if the endpoint can't be determined from
- How to handle non-canonical/redirecting profile URLs? Since the ticket itself never goes through the full IndieAuth flow, the granter can't get the canonical identity URL from the
- As of this change in Publ, if Publ gets a ticket request for a page with a
rel="canonical", it forwards the request to the canonical page. No trust is assumed based on the original requesting URL, and the ticket only goes to the canonical URL's declared endpoint.
- As of this change in Publ, if Publ gets a ticket request for a page with a
Martijn van der Ven
Please treat the whole thing as experimental, and feel free to test your own endpoints against it!
Tokens created through there are meant for the entire domain (the
https://vanderven.se/) and have no
scope set. They are purely to identify someone as their URL. Because of the experimental phase of this spec, tokens are kept to short life times (3 days).
- Special: while requesting tickets is declared out-of-scope for this extension, the way it is implemented here follows the pattern from IndieAuth’s Token Revocation. A
action=tickettriggers the sending of a ticket.
As of 2021-07-04, David Shanske has a proof of concept ticket endpoint on his test site that can receive tickets and redeem the associated token and store it. As of 2021-07-10, it has a management interface but no redemption methodology.
This implementation requires an
issuer to reference the profile URL of the user who's
token_endpoint is to be used for the ticket exchange.
This returns the token exchange body to the caller, and logs some information, but not the granted access token (currently), nor does it store it.
It does not yet utilize it for anything or have an interface to do so.
Jamie is currently using this on his testing identity.
- Manually request a ticket by going to
- Send a POST request to
scope=REQUESTED_SCOPES(per the proposed mechanism of a ticket request, as of 2022-03-20)
- Refresh tokens (per standard OAuth2)
- Log in to the test site and a ticket will be automatically sent to you
As of 2021-07-10, Aaron Parecki has implemented both sending and receiving tokens on a standalone test website.
gRegor Morrill: 2023-11-05: on my staging site, implemented sending tickets from the admin and the token endpoint can redeem a ticket for an access token. Also set up a ticket_endpoint that receives and logs the request, but does not yet exchange the ticket.