Skip to content

Add draft authtoken spec#602

Open
skizzerz wants to merge 2 commits intoircv3:masterfrom
skizzerz:authtoken
Open

Add draft authtoken spec#602
skizzerz wants to merge 2 commits intoircv3:masterfrom
skizzerz:authtoken

Conversation

@skizzerz
Copy link
Copy Markdown
Contributor

This spec aims to be a replacement for the EXTJWT pull request by specifying a more general mechanism for generating and validating authentication tokens. Unlike EXTJWT, this does not limit the tokens to the JWT format, instead strongly preferring single-use OTPs (although JWTs are still permissible and supported by the spec).

Validation occurs through the IRC protocol as well, to reduce implementation burden on servers (it's just another command, rather than needing to spin up an httpd or other external service).

This spec aims to be a replacement for the EXTJWT pull request by
specifying a more general mechanism for generating and validating
authentication tokens. Unlike EXTJWT, this does not limit the tokens to
the JWT format, instead strongly preferring single-use OTPs (although
JWTs are still permissible and supported by the spec).

Validation occurs through the IRC protocol as well, to reduce
implementation burden on servers (it's just another command, rather than
needing to spin up an httpd or other external service).

Co-authored-by: whitequark <whitequark@whitequark.org>
@whitequark
Copy link
Copy Markdown

Ergochat has a PR implementing draft/AUTHTOKEN (ergochat/ergo#2385), I'll let @slingamn comment on it though.

Copy link
Copy Markdown
Contributor

@slingamn slingamn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated my PR implementation. Overall I really like this spec!

Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md

```
Client:
TOKEN VALIDATE FILEHOST https://upload.example.com :54a333f6e0c218382ab5f64c1135f07798d0255e87115c451ba67cc440f5ea84
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need the URL as a parameter here? The client can validate it from the batch parameter in the reply batch. It may also slightly constrain the lengths of URLs that can be used (since a single-line token can be up to 200 bytes).

Copy link
Copy Markdown
Contributor Author

@skizzerz skizzerz Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reason for the URL here was to prevent token confusion attacks. Say an ircd is migrating from an old filehost to a new one; once the migration happens, we don't want users to be able to upload to the old filehost anymore. If all that was passed was the service key, we have no real means of preventing this without trusting the client. If the service also passes its own URL, then the server can recognize that it's not meant to be interacting with that service and reject the validate attempt. I think it's important to keep, which is partly why the byte limit for single-line tokens is 200 in order to give more space for the URL.

Edit: your suggestion to recommend a different key per service is another way to avoid the above scenario while not requiring a URL parameter.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally clear on whether the threat model here is accident or fraud, so to speak. If it's fraud, the new service's URL is not actually a secret, so nothing prevents the old service from passing it instead of its own URL. If it's accident, the service can guard against this itself by validating the URL returned in the batch parameters (the same way JWT consumers normally validate the aud claim).

Rotating the key would work too if the tokens are JWTs signed with a unique per-service key, or MAC'ed UUIDs with a key held only by the ircd. But I'm not sure that's even relevant given what we're trying to prevent.

It seems like maybe the real concern here is usability, not security, in that we could use some way to push updated service lists to clients?

Copy link
Copy Markdown

@furudean furudean Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if a CAP to subscribe to updates like authtoken-notify would be useful to combat the usability problem. If negotiated, the ircd could output the TOKEN SERVICELIST on changes or any other relevant notify to tell the client that things have changed.

Reconnection being the only way to receive external service updates seems undesirable. That encourages polling, which is just inefficient overall. Your client could track the URL parameter, but I don't like that as much as having an implied mechanism for it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the threat model is "the old filehost has turned evil, and is trying to impersonate the user to the new filehost", that seems generally impossible to prevent in this design (with or without interactive validation), since it's equivalent to MITM of the bearer token.

Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated
Comment thread extensions/authtoken.md Outdated

- The service must validate that the token is intended for that service, to avoid token confusion attacks. Using URL-based validation (i.e. checking that one of the claims of the token matches the URL of the service) is a good way to do this. For JWT, this would be the "aud" claim.
- The service must validate that the token came from the IRC server it is expecting tokens to come from. For JWT, this would be the "iss" claim.
- The service must validate that the token is signed by the IRC server with a secure algorithm and that the signature is valid. The signing keys (recommended) or secrets (not recommended) should be pre-shared out of band.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "secrets" intended to refer to symmetric signing keys used by, e.g. HS256? Discouraging this seems unwarranted to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this section was going over what claims should be present in tokens that are non-interactively validated (i.e. the service is not using TOKEN VALIDATE). In such an event, HS256 or similar symmetric algorithms means that a secret key is being shared between multiple parties, since the specification makes no assumption that the ircd and external service are being run by the same operators. This in turn means that the external service could spoof its own properly-signed tokens without the ircd's input, for whatever that might allow it to do (in theory not much, but the risk exists that there are now multiple parties able to issue valid signed tokens).

If the ircd is intending for interactive validation only (i.e. external services must use TOKEN VALIDATE to get the claims list), then HS256 is perfectly appropriate because the secret key only lives in one place: on the ircd. Doing public key in such scenarios would be overkill and unnecessary.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I was thinking that if you're doing one key per service, there would be no real disadvantage to using a symmetric secret key. But actually, with a symmetric key, the service would be able to sign its own tokens and then submit them for online validation, possibly learning some user data via TOKEN CLAIM that it might not normally be able to access. (If alice uses QDB and but bob never does, QDB would have normally have access to alice's TOKEN CLAIM outputs, but not bob's.) Probably not a big deal, but a real consideration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggests another hardening recommendation for JWTs: if interactive validation is not needed, it can be administratively disabled. And then there's really no problem with HS256 with unique keys: the service can forge whatever token it wants, but it's the only system that will accept the token.

If interactive validation is required, I think a unique asymmetric keypair per service should work (the ircd has the private key, the service has the public key, the general public has nothing).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This in turn means that the external service could spoof its own properly-signed tokens without the ircd's input

Is it realistic to expect there would be services that are hosted by a potentially unstrusted 3rd-party and yet require the IRCd as authorisation provider? That's a weird combo if the IRCd isn't a generic auth provider, and external services instead have to be manually added to the SERVICELIST by network operators.

What is the intent of this spec? Allowing networks to host additional services with SSO but without HTTP-dependent standards like OIDC, or is it turning networks into generic decentralised authentication providers, like a IRC-based Open-ID clone?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent is for federated authentication and authorization with allowlisted services, which may or may not be run by 3rd parties. Partnering with a 3rd party for some service doesn't imply a full-trust relationship, and it makes sense to limit the blast radius of potential security issues as much as possible in the event that the 3rd party is compromised. HTTP-based mechanisms are not specified because IRC is not HTTP, and there are likely going to be non-HTTP-based services that wish to integrate with IRC for authn/authz decisions in this manner.

By specifying both the token generation and token validation layers, these services do not need to ship a validation implementation on a per-ircd basis. This increases interoperability by allowing network operators to choose services based on criteria other than "do they support the specific magic incantations needed to talk to the ircd I'm running"

As a note, while JWTs are popular (which is why there is a lot of space devoted to talking about them), the spec recommends single-use randomly generated OTPs and interactive validation. These are significantly more secure, more compact, and much easier to implement. The JWT discussion is primarily around how to avoid the numerous footguns that the JWT specification comes with and have resulted in security vulnerabilities in other services, without the implementer needing to be an expert in JWT security and/or having done all of that prior research. There are a lot of really bad JWT implementations out there and I don't want this spec to be another one in that pile.

Comment thread extensions/authtoken.md
Comment thread extensions/authtoken.md
BATCH -12345
```

### VALIDATE subcommand (batched)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ingesting C2S-batched VALIDATE was the hardest implementation step for me. I'm not sure there's a better option overall, but I wanted to air this issue: C2S batches might seem like they simplify server ingestion of multiline requests, but I don't think they do, because each type of C2S batch will have its own application-specific constraints, length limits, and abuse risks, so you can't really write a generic "ingest C2S batch" handler. (For example, in the Ergo implementation, we turn off fakelag when a draft/multiline batch is in progress, so we need to make sure we turn it back on on every fail path that ends the batch.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another pain point for me was splitting input validation and permissions checking logic between the BATCH and VALIDATE handlers.

This would actually be cleaner for me if it were just:

VALIDATE TOKEN <servicename> <single-line token under 300 bytes>

or else

VALIDATE TOKEN <servicename> <multi-line token chunk exactly 300 bytes>
[repeat zero or more times]
VALIDATE TOKEN <servicename> <final token chunk under 300 bytes, possibly 0 bytes>

I invite comment as to whether this is cleaner in general, or just an idiosyncratic constraint imposed by my implementation :-) I would note in passing that VALIDATE TOKEN is not intended for use in "normal" IRC traffic, so the full extensibility of batch (e.g. allowing nested batches, allowing interspersed messages that are not part of the batch) is not directly relevant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My solanum C2S batch implementation involves a generic handler that works across every registered batch type to collect the batched lines together and fire off whatever function is supposed to handle the batch once the entire thing is received, so your inability to do that is certainly an ergo limitation rather than an inherent limitation of C2S batches (this is just an observation; I'm not saying that your argument lacks merit). For reference/comparison, solanum doesn't turn off fakelag for C2S batches. It might take a while to send it but once fully sent it gets burst out all at once.

I originally had it worded to be like AUTHENTICATE where each intermediate line was a fixed byte count and the final line was the leftovers (or blank), however I felt that C2S batches were a more elegant solution that requires fewer bespoke per-command multiline handlers on the client and server side -- AUTHENTICATE is 400 bytes, this could be 400 or might be 300, something else may choose some other limit, etc. I'd also like to drive more adoption of C2S batches in general as a useful framing tool for things that take up more than a line.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, from the n=2 sample I have, I'm not convinced that C2S batching is a savings on implementation complexity. From my point of view, the server implementation still needs (1) custom validation rules, per batch type, about what can or can't be in the batch (2) custom length limits (which also have to be batch-type-aware). And then there generally seems to be a conservation effect, where any validation or limiting logic you try to make generic at the ingestion stage has to be made up later. (For example, you could try to implement generic length limits based on total byte size of the messages being queued, but you have to apply the real length limit later after doing whatever application-specific aggregation is required.)

One of the things that makes C2S batching compelling for draft/multiline is the interaction with labeled-response and echo-message (the client labels the initial BATCH line, then gets a labeled echo message after the whole batch is complete). What are some of the other use cases you're thinking about for C2S batching?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are some of the other use cases you're thinking about for C2S batching?

Rich content (aka "embeds" in discord or "blocks" in slack) is the main one I have in mind. Basically, a message that contains some title, content, optional image, optional buttons along the bottom, etc. I don't have anything written down for that yet but C2S batch would be the natural way of sending that and collecting all of those disparate things together as a single message.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other way of doing that is just a client tag. e.g. ircv3/ircv3-ideas#22 although the fallback mentioned there could potentially be batched along with it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess also if we wanted to go over 4 KB we would have to batch it?

- Reword a couple of sections to make motivations clearer.
- Make SERVICELIST the canonical place for services to live, avoiding
  duplication in ISUPPORT or CAP. The draft/authtoken cap was made a
  requirement to support this feature, although the C2S batch that it
  enables remains optional.
- Make SERVICELIST replies use a batch instead of NOTE for the standard
  case.
Comment thread extensions/authtoken.md
A user sends `TOKEN SERVICELIST` on a network where no services are configured. As such, they receive a note that no services are defined.

```
NOTE TOKEN NO_SERVICES :No services are defined for this network.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would TOKEN NO_SERVICES :No services are defined (i.e. NO_SERVICES as a new subcommand of TOKEN) make it easier for clients to route this message to a handler that clears the service list?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't be easier necessarily, but I agree that a NOTE might not be the right reply, since NOTE seems to generally be informational, but I do feel that using a Standard Reply here is right, perhaps WARN instead? Slightly more actionable than a NOTE in terms of client handling

Comment thread extensions/authtoken.md Outdated
TOKEN SERVICELIST
```

The `TOKEN SERVICELIST` subcommand provides a list of all recognized service keys as well as their corresponding URLs. The description parameter provides some sort of description about the service.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really the right spec to list services?
Wouldn't it be cleaner to have a separate spec for discovering external services? It could also list services that do not require a token.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary for a client to discover service capabilities.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AIUI this is intended as the unique discovery mechanism for services that use tokens. Services that do not use tokens can be discovered via 005 (as with draft/filehost for example).

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.

8 participants