Skip to content

EXTJWT command for integrating external web services#547

Open
ItsOnlyBinary wants to merge 12 commits intoircv3:masterfrom
ItsOnlyBinary:extjwt
Open

EXTJWT command for integrating external web services#547
ItsOnlyBinary wants to merge 12 commits intoircv3:masterfrom
ItsOnlyBinary:extjwt

Conversation

@ItsOnlyBinary
Copy link
Contributor

@prawnsalad
This spec provides a way for web services hosted externally to an IRC server to authenticate users that are connected to the IRC server by making use of the standard JWT tokens (https://jwt.io/).

This allows a web service to do things such as:

* Granting admin access to a networks wiki page if a user has +o on the network

* Granting write access to a channels wiki page if the user has +o in the channel

* Automatically creating a user account if the user is logged into the IRC server and has an account name

For a more indepth example we could use the free audio/video conference service - Jitsi Meet. This service has built in JWT verification in that an application can send a user to a URL that contains a JWT token, and if the Jitsi Meet server verifies this token successfully, the user is granted access to that conference room. When an IRC client wants to join a conference room, it would first call EXTJWT #testchannel to receive a JWT token from the IRCd. The client would then open a browser window navigating to the Jitsi Meet URL while passing that token. It is up to the client to decide how and where to use this token, eg. via a "Jitsi Call" button for example.

As prawnsalad has stepped back from IRC development and I have taken over Kiwi IRC development. I cannot allow this ircv3 specification to be abandoned as Kiwi IRC uses it for quite a few plugins. (jitsi conferencing, file uploads, account avatar uploads)

So I have also forked #341 and updated based on comments from within the previous pull-request.

@ItsOnlyBinary
Copy link
Contributor Author

ItsOnlyBinary commented Jul 1, 2024

Reasoning behind removing the vfy claim:

Having the verify url within the token would encourage implementations to use the url without verifying its trusted, allowing the spoofing of tokens.

The verify url should be a pre-shared component for trust, just like the key would be pre-shared.

With the url having the advantage of not actually sharing the key, so that the host of the third party service could not actually generated trusted tokens.

@sadiepowell
Copy link
Contributor

@jwheare
Copy link
Member

jwheare commented Apr 3, 2025

Can we get an audit on implementations for this. Those I'm aware of:

Anyone else? Particularly would be nice to see clients, with examples of it being used.

@jwheare
Copy link
Member

jwheare commented Feb 7, 2026

@sadiepowell @k4bek4be @slingamn thoughts on whether this should be merged as a draft? (needs editing to reflect that)

AFAIK no client implements this other than kiwi, but any insight on how widely this is used on your servers?

@sadiepowell
Copy link
Contributor

I'm happy to see this merged as a draft. We have a few users who are using this with Kiwi. I'm not aware of any other client implementations.

@slingamn
Copy link
Contributor

slingamn commented Feb 8, 2026

I also support merging this as a draft. I don't have any concrete feedback from operators to report.

Copy link
Contributor

@skizzerz skizzerz left a comment

Choose a reason for hiding this comment

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

Before we merge as draft, I took a look and there are a handful of issues I see with the spec as-written.

I'm not particularly happy with sub being the user's nickname (an ephemeral identifier that is very easy for the end user to change), but not every network has accounts, and for those that do, there's no guarantee an accountname is stable either. As such, I can't think of anything better. We may want to expand sub to allow for other identifiers however if the implementation has something more stable they'd wish to use. e.g. an account ID.

Note: I didn't modify the examples in my suggestions since there's no consensus on moving forward with them yet. If we decide to rename some of these claim names, then examples should be updated accordingly.

The client will send at minimum `EXTJWT *` to the server to request a new JWT token. The server MUST then reply with the above response syntax with the `requested_target` and `service_name` defaulting to `*` if they were not provided in the request. The JWT token MUST contain the following claims that are relevant to the client at that time:

* `exp` Number; Unix timestamp for when this token expires. See below for notes on the expiry claim.
* `iss` String; The server name that generated this token.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* `iss` String; The server name that generated this token.
* `iss` String; The server or network name that generated this token.

Networks that implement server hiding may wish to mask exactly which server produced a given JWT, or find that it isn't necessary to identify specific servers and prefer to have a stable issuer regardless of which server the user connected to.

Copy link
Contributor

Choose a reason for hiding this comment

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

Using the network name is an acceptable alternative but FWIW in our implementation on networks that hide server names we use the masked server name (like "irc.example.com") here instead of the actual server name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should the masked/common server name be the default here (eg irc.example.com). for 3 party services wanting to support multiple networks having the common name would be much more useful than a specific server. The other option would be to have both.


If the command sent by the client includes a service name, Eg. `EXTJWT * jitsi`, the server MUST then reply with the service name as its `service_name` parameter, along with the JWT token containing the above claims and also the following claims relevant to the service:

* `service` String; The configured service name that can be used to verify the token, with either a pre-shared key or url.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* `service` String; The configured service name that can be used to verify the token, with either a pre-shared key or url.
* `aud` String or []String; The configured service names or URIs that the token is intended for.

aud is a standard claim type and service was duplicative of that standard audience claim. Let's just use the standard one instead of inventing our own. Expanded to allow either a string or an array of strings per JWT spec, and in case the service_name identifies multiple backend services the JWT should be valid for.

If the command sent by the client includes a channel name, Eg. `EXTJWT #channel`, the server MUST then reply with the channel name as its `requested_target` parameter, along with the JWT token containing the above claims and also the following claims relevant to the channel at that time:

* `channel` String; The channel name this token is related to.
* `joined` Number; Unix timestamp of the time at which the client joined the channel. 0 if not joined. 1 if no timestamp is available.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* `joined` Number; Unix timestamp of the time at which the client joined the channel. 0 if not joined. 1 if no timestamp is available.
* `joined` ?Number; Unix timestamp of the time at which the client joined the channel. `null` if not joined. 0 if no timestamp is available.

Similar to account, we can make values nullable so trying to do that to indicate lack of value makes more sense than identifying special sentinel values.

* `exp` Number; Unix timestamp for when this token expires. See below for notes on the expiry claim.
* `iss` String; The server name that generated this token.
* `sub` String; The nick of the client that requested this token.
* `account` String; The account name of the user that requested this token. Empty if not available.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* `account` String; The account name of the user that requested this token. Empty if not available.
* `account` ?String; The account name of the user that requested this token. `null` if not available.

JSON allows null values, and JWT allows any JSON values. Making these values nullable to designate a lack of value rather than designating certain strings as "special" makes a lot more sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My concern with this is adding complexity in other languages, while json supports null using it could add unneeded complexity in languages with strict type handling.

Copy link
Contributor

Choose a reason for hiding this comment

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

Such as? I cannot think of a single major language that would have an issue with this. Anyone implementing this would be using a JSON parsing library rather than handrolling a parser, and all mainstream JSON parsing libraries in existence support null just fine, mapping it into whatever that language supports.

If you mean extra complexity checking for null vs a value, that complexity already existed; it just wasn't encoded into the type system but rather as special values. Which is worse because it's easy to forget or incorrectly write those checks, whereas the type system enforcing you to check for null (in languages that enforce that) makes it impossible for you to forget or implement it incorrectly. At the very least, doing it wrong will result in obvious runtime behavior (crashes/exceptions) rather than silent corruption.

Copy link
Contributor

Choose a reason for hiding this comment

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

My concern with this is adding complexity in other languages, while json supports null using it could add unneeded complexity in languages with strict type handling.

For what its worth most languages like this will represent such a value using the native null value or in the case of something like Rust, Option<String>.

@k4bek4be
Copy link

k4bek4be commented Feb 8, 2026

Server support: I know there is small amount of users using EXTJWT in UnrealIRCd. I'll be trying to get some more precise numbers.
Client support: The PIRC.pl web client uses it. But the client probably is not used outside of our IRC network (also source code of the service consuming JWTs is not yet released publicly).
No functional issues were noted with current implementation, and I hadn't thought much about improving underlying ideas.

@ItsOnlyBinary
Copy link
Contributor Author

Kiwi IRC currently has 3 main plugins that use EXTJWT they are:

kiwiirc/plugin-fileuploader allows file uploads that can be restricted to logged in accounts only or allow logged in account files to be retained for longer.

kiwiirc/plugin-conference integrates jitsi-meet in to channels and queries allowing for video conferencing with IRC. when used with a jitsi-meet side auth plugin this can be used to control who is moderator of the conference based on IRC channel mode and also restrict channels etc.

itsonlybinary/kiwiirc-plugin-avatar-upload allows logged in users to upload avatars which can then be shown to all users.

Other network admins do appear to have also created quite complex account/profile systems that also use EXTJWT tokens to link different pieces.

I hope these demonstrate the usefulness of this spec for linking IRC with 3rd party services

@Celelibi
Copy link

Celelibi commented Feb 9, 2026

I really like this spec and see great potential for use if paired with an external-service discovery mechanism.
Something that could provide the clients with a list of external services with their infos (name, type, URL with token placeholder).
Maybe I misunderstood the spec, but it looks like it requires the client to know which external service exist, their name and their URL. It is therefore limited to clients specific to a given server.

Also, (it might be too late for this, given several implementations exists, but I'm proposing anyway), EXTJWT seems like a very specific verb that doesn't lend itself to other token generation protocols in the future. May I suggest s/EXTJWT/EXTTOKEN JWT/g? ^^
Just to leave room for EXTTOKEN OAUTH or whatever gets popular or requested.

@slingamn
Copy link
Contributor

slingamn commented Feb 9, 2026

re. the use of aud instead of service and the nullable fields, I think these changes are improvements in principle but I'm not sure the improvements are sufficient to justify the compatibility breakage. Do other people have thoughts?

@sadiepowell
Copy link
Contributor

Given its still a draft spec I'm fine with breaking compatibility to make the IRCv3 implementation behave more like the upstream spec, if necessary server implementations can emit both keys for a period.

@slingamn
Copy link
Contributor

slingamn commented Feb 9, 2026

There's no way to make the nullable field changes compatible since they use the same key names.

@jwheare
Copy link
Member

jwheare commented Feb 10, 2026

Since there's no prefixing here (this spec predates ISUPPORT prefixing, no need to add it now imo) there's not much to add in a "Notes for implementing work-in-progress version" section. The wip key is also already set.

So this is fine to merge once a decision is made on the other suggestions. I'd probably favour not changing the nullable fileds. The iss wording suggestion seems fine. I'm neutral on changing service to aud, probably not worth it though.

@skizzerz
Copy link
Contributor

The more I look into this spec the less I like it. While I'm not going to expend any additional energy to block it from being merged or correct it, I think this is probably better off left as a vendor extension (unprefixed for historical reasons) and something goes back to the drawing board from square 1 with a better eye on security (and a different command name/ISUPPORT token). It attempts to implement authn and authz and fails at both because it tries to be too generic without any consideration of the threat models it is facing or that external services making use of this face.

  • The channel form of the command will introduce security issues in external services for anything that needs to vet beyond "is this nickname a member of the channel" -- and may introduce security issues in external services even for that. Nicknames are both ephemeral and reusable identifiers, expiration can last longer than that nickname's membership in the channel, there is no revocation or other backchannel checks to ensure the token is still valid.
  • Similarly for sub, same issue with nicknames and its presence on the server (rather than just channels). It ranges from largely useless to actively harmful as an authn identifier. If the service needs persistent ids, something persistent should be used. If wants transient ids, the generation scheme should avoid re-use.
  • For the service form, the spec honestly needs less structure regarding the claims here. umodes are largely meaningless (especially o for oper since every modern ircd under the sun has different oper privs so simply knowing someone is an oper doesn't tell you what they're able to do). There's no reason to include useless crap in the JWT. The admins need to configure the services in conf anyway, they can specify exactly what that service needs and employ a least-privilege approach to what claims are passed.
  • aud is an important security control and is missing entirely, and there is no protection against replay attacks.

While I do not mean to disparage the writers of this spec, it's fairly clear that minimal to no research was done on the federated authn/authz front. Please look at the security challenges that SAML and OIDC solve and how they solve them, or heck even OAuth in general with their Bearer tokens. I'm not saying this needs to replicate those precisely, but it should not regress in basic ways that make the spec inflexible and largely unusable in general, despite supposedly being general purpose.

As mentioned before, I'm not going to expend any additional energy on fighting this, so please do not expect further replies in this thread from me.

@sadiepowell
Copy link
Contributor

there is no revocation or other backchannel checks to ensure the token is still valid.

InspIRCd supports the vfy key which provides an endpoint that can be used for this purpose but I don't think it ever made it into the spec.

@ItsOnlyBinary
Copy link
Contributor Author

I removed vfy after reading #341 (comment)

I explain how a pre-shared verify url can be used in "external service verifies that the token has not been tampered with by one of two methods" this wording really should have security considerations notice, suggesting that both should be done and the risks of only using pre-shared password.

I will do some research into hardening and improving this spec. I am fully open to specific suggestions and wording.

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