# What we opened under Apache-2.0, and what we did not

Abblix OIDC Server is a commercial library. As of 2.4, the Security Event and JWT packages, together with the two utility packages they stand on, are Apache-2.0.

In full, the open set is:

- `Abblix.SecurityEvents` - [Security Event Tokens](https://www.abblix.com/en/docs/glossary-overview#set) ([RFC 8417](https://datatracker.ietf.org/doc/html/rfc8417)); the receiving side of push and poll delivery ([RFC 8935](https://datatracker.ietf.org/doc/html/rfc8935) and [RFC 8936](https://datatracker.ietf.org/doc/html/rfc8936)), that is, accepting a pushed token and polling a transmitter for pending ones, though a poll feed presupposes a stream and the stream client is commercial; the subject identifier formats of [RFC 9493](https://datatracker.ietf.org/doc/html/rfc9493) together with the additional ones the [Shared Signals](https://www.abblix.com/en/docs/glossary-overview#ssf) profiles use; fetching an issuer's [JWK](https://www.abblix.com/en/docs/glossary-overview#jwk) Set to verify what arrives; and the receiving half of OpenID [Back-Channel Logout](https://www.abblix.com/en/docs/glossary-overview#back-channel-logout).
- `Abblix.SecurityEvents.CAEP` and `Abblix.SecurityEvents.RISC` - the event vocabularies, typed payloads and event type constants.
- `Abblix.SecurityEvents.MinimalAPI` - the ASP.NET Core routes for the above.
- `Abblix.JWT` - the JWT and [JWE](https://www.abblix.com/en/docs/glossary-overview#jwe) engine underneath: signing, verification, encryption, the JWK and JWK Set model.
- `Abblix.Utils` and `Abblix.DependencyInjection` - the plumbing the packages above stand on.

They target net8.0, net9.0 and net10.0, they are on nuget.org at 2.4.0, and the source is at [github.com/Abblix/Oidc.Server](https://github.com/Abblix/Oidc.Server). Each declares `Apache-2.0` in its package manifest and carries an SPDX header per file. The package ids above are spelled as nuget.org shows them; the namespaces you write in a `using` follow C# convention instead, so `Abblix.JWT` on nuget is `Abblix.Jwt` in code, and `Abblix.SecurityEvents.MinimalAPI` is `Abblix.SecurityEvents.MinimalApi`.

As of 2.4.0 the set is closed: no package in it references a package we sell, and its other dependencies are the Microsoft extension libraries plus, on net8.0 and net9.0, `System.Linq.Async`. Nothing enforces that in our build, so read it as a fact about this release rather than a guarantee about the next: `dotnet list package --include-transitive` on a project that references only these packages prints the whole set. Run it.

A published version also stays published. The Apache-2.0 copyright grant is irrevocable by its own terms, so the releases you already build on cannot be withdrawn. That is a statement about the artifact and not about the roadmap, so here is the part it does not cover: 2.x is supported until November 2028 and 3.x is planned for November 2026, both on the [version support lifecycle](https://www.abblix.com/en/docs/version-support-lifecycle) page, and we have not decided what license the 3.x line carries.

## What this gives an application that is not our customer

ASP.NET Core's OpenID Connect handler implements front-channel sign-out. Its `RemoteSignOutPath` endpoint accepts a POST, which makes it look like the answer, but it matches `sid` and `iss` against the stored ticket and never reads a Logout Token. An application whose provider posts one to it needs an endpoint of its own, with token validation and a replay check behind it. It does not have to be our customer to get them:

```csharp
using Abblix.SecurityEvents.BackChannelLogout;
using Abblix.SecurityEvents.Infrastructure;
using Abblix.SecurityEvents.MinimalApi;

// Replay reservations ride the host's IDistributedCache. In memory they die with the
// process, so a restart re-opens the window for every unexpired token.
builder.Services.AddDistributedMemoryCache();

builder.Services.AddSecurityEvents();

// Asks the issuer where its keys are.
builder.Services.AddDiscoveryKeyResolution();

builder.Services.AddBackChannelLogoutReceiver(new BackChannelLogoutValidationOptions
{
    ExpectedIssuers = ["https://op.example.com"],
    ExpectedAudience = "this-client-id",
});

builder.Services.AddSingleton<ILogoutNotificationSink, MySessionStore>();

app.MapBackChannelLogoutEndpoint("/backchannel-logout");
```

A word on the replay check, because its cost is not obvious. Each accepted token reserves the issuer and the `jti` together for the remainder of that token's lifetime, so the store holds one small key per token in flight and expires them itself. The reservation rides `IDistributedCache`, which offers get and set and no compare-and-set, so it is read-then-write: two simultaneous presentations of one token can both be told they are the first. Pointing that cache at shared Redis makes the reservations shared, not atomic. For [Back-Channel Logout](https://www.abblix.com/en/docs/glossary-overview#back-channel-logout) a lost race costs one duplicate call into a sink that has to be idempotent anyway, which is why we ship it this way. A deployment that wants a reservation that genuinely refuses derives `ReplayCacheBase` over its own store's conditional write, and ours steps aside.

The sink is the part that takes real work, and no library closes it for you. Be concrete about it before planning around it. You are handed a session identifier, or - when the provider does not track sessions - only a subject, which means every session that user holds with you. An application on the stock ASP.NET Core handler with cookie authentication usually has neither to search by: no server-side session record to delete, and no `sid` kept from the ID token. Making back-channel logout work there means introducing a server-side session store keyed by subject and session, and capturing `sid` at sign-in. The receiver saves you the token work, not that work.

Two properties your sink owes. It has to be idempotent, because a provider may re-send a token it suspects was not delivered, and may end the same session twice after a re-login. And its return value is a statement to the provider: nothing answers 200, a description travels back inside a 400. A sink that swallows its own failure and stays silent tells the provider the sessions are closed when they are not.

`Abblix.JWT` stands alone as well. Signing, verification, encryption and a replay cache over `IDistributedCache`, with no OpenID Connect server anywhere in its dependency graph. Two things to know before building on it: fetching an issuer's key set over HTTP lives one package up, in `Abblix.SecurityEvents`, and the external key custodians for HashiCorp Vault and Azure Key Vault are commercial, so supplying keys from your own store is an interface you implement. With that said, if a [JWE](https://www.abblix.com/en/docs/glossary-overview#jwe) implementation is all you came for, take it.

## What is commercial

The OpenID Provider itself, `Abblix.OIDC.Server` with its MVC and Minimal API adapters. The [Shared Signals](https://www.abblix.com/en/docs/glossary-overview#ssf) layer, `Abblix.SharedSignals` with its adapter and its Redis backing, which carries streams: creating and configuring them, the management API, the subject bookkeeping, and delivery from the transmitter side. The external key custodians, `Abblix.JWT.Vault` and `Abblix.JWT.Azure`.

Commercial does not mean paid for everyone. Our license gives free production use to companies under a million dollars in annual revenue and under a million raised, to non-profits and educational institutions, and to individuals working on a personal project: one production issuer, no cap on users or client applications, and no feature of the OpenID Provider withheld. The free tier is the whole product rather than a subset of it. Development, test and staging deployments are free at any size, and so are non-commercial open-source projects. Separately licensed extensions, such as the Shared Signals packages above, are what they sound like. Past those thresholds the provider itself needs a paid license, and that is the business.

## Where the line actually falls

[Back-Channel Logout](https://www.abblix.com/en/docs/glossary-overview#back-channel-logout) needs no stream, so the receiving side of it is entirely in the open set: the endpoint, the validation profile, the replay reservation and the response shaping. A [Shared Signals](https://www.abblix.com/en/docs/glossary-overview#ssf) integration is different. The event vocabularies are open and the token pipeline is open, but the client that registers a stream with a transmitter and reads its configuration lives in `Abblix.SharedSignals`, which is commercial - and that holds for receiving a feed as much as for sending one. If you are consuming [CAEP](https://www.abblix.com/en/docs/glossary-overview#caep) or [RISC](https://www.abblix.com/en/docs/glossary-overview#risc) events from another provider, you license that package or write the stream management yourself over the open pipeline.

## Why the line falls there

Two things had to be true at once for a package to be opened. It had to be something the ecosystem is better off sharing, and it had to be something other than what we sell.

A Security Event Token is an envelope: what it is, whom it is about, how it is validated, how it travels. Nothing in that description belongs to a vendor, and the validation profile it needs - the `typ` check, the refusal of `exp`, the required `jti` - should exist once rather than in every application that receives a notification. The same holds for the [CAEP](https://www.abblix.com/en/docs/glossary-overview#caep) and [RISC](https://www.abblix.com/en/docs/glossary-overview#risc) vocabularies: an event dictionary only one library understands is not a dictionary.

Streams are the other half: creating one, pausing it, tracking who may hear about which subject, delivering to receivers that are down, doing it from more than one instance. That is operational machinery, and it is the part we sell. So the envelope is open and the machinery is not.

## What we have and have not tested

The receiver is provider-agnostic by construction: it trusts an issuer allowlist and a JWK Set, and knows nothing about who signed. What that is worth in evidence: we exercise it end to end against our own provider, and it has not been through the OpenID Foundation relying-party conformance profile for back-channel logout. We have not published an interoperability run against a third-party provider.

If yours is one, the integration is one issuer, one audience and one JWKS away, and the thing we most want back is a defect report carrying the token that failed.

## Contributions, competitors and support

The commercial packages named above are the business, and nothing here says the rest follows later.

The project is developed in-house, and the license does not change that: we do not merge external pull requests, including into the Apache-2.0 packages. Remaining the sole author is what keeps the licensing decision ours for future releases, including the freedom to keep releasing under Apache-2.0, which a mixed copyright would complicate. Forking an open package and changing your copy is what Apache-2.0 grants. What we will not do is take the change back into this repository, so what we can act on is a defect report with a reproduction, and we would rather have one than a patch we cannot take.

The license also settles who may use the code, and that includes people we compete with. Apache-2.0 carries a patent grant and no field-of-use restriction, so another .NET identity product can ship `Abblix.SecurityEvents` or `Abblix.JWT` inside something that competes with ours. We knew that when we chose it. The patent grant stands unless the user sues over the code, which is Apache-2.0's own Section 3 and not something we added.

That permission stops where the open set stops. The commercial packages are governed by our own license, which does not allow their source to be used for building a competing product, and reading them in the repository is evaluation access rather than a grant. The two halves sit in one repository and are told apart the same way in every case: by the license expression in the package and the SPDX header in the file.

Contractual support comes with the commercial products, with response times set in the license. The open packages have the issue tracker and the discussions at the repository above: we triage new issues weekly and reply to every bug report. Since we will not merge your patch, that responsiveness is the thing to judge us on, and the tracker's history is where to judge it.
