-
Notifications
You must be signed in to change notification settings - Fork 8
Separate authentication and entitlements, add federated DRM #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d456f05
a19a389
049943d
b1becdb
a3730e6
2b50184
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,169 @@ | ||
| # Restricted Packages | ||
|
|
||
| FAIR builds the concept of "restricted" packages right into the protocol. These are packages which require some form of authentication, such as a token or a username and password. | ||
| FAIR builds the concept of "restricted" packages right into the protocol. These are packages which require some form of entitlement, such as a subscription, purchase, or license key. | ||
|
|
||
| In the WP ecosystem, many types of restricted packages are available, including privately-published plugins and premium plugins. FAIR builds support for these into the protocol. | ||
|
|
||
| FAIR separates two distinct concerns: | ||
|
|
||
| ## Indicating a restricted package | ||
| - **Authentication** (`auth` on releases) — How to present credentials to the repository's HTTP server. This is the mechanism (bearer token, basic auth, OAuth2). | ||
| - **Entitlements** (`entitlements` on metadata) — What a user needs to be allowed to access the package. This is the policy, controlled by the vendor. | ||
|
|
||
| To indicate a restricted package, your package metadata can specify an `auth` property, indicating that the package is only available for authorized users. | ||
| This separation means that vendors control access to their packages regardless of which repository serves them, and users keep their entitlements when packages move between repositories. | ||
|
|
||
| In the FAIR plugin, two types of authentication are supported: | ||
|
|
||
| * `bearer` - This type indicates that a bearer token (such as an API key) is required. | ||
| * `basic` - This type indicates that a username and password is required. | ||
| ## Setting up entitlements | ||
|
|
||
| The `hint` property can be provided to provide human-readable text indicating why authentication is required, and `hint_url` provides a way to link users to more information or a purchase page. | ||
| ### 1. Add an entitlement service to your DID Document | ||
|
|
||
| For example, a premium plugin could provide the following: | ||
| Register a `FairEntitlementService` in your DID Document pointing to your license/entitlement server: | ||
|
|
||
| ```json | ||
| { | ||
| "auth": { | ||
| "hint": "Example Plugin requires an active subscription. Visit the link to purchase it, or enter your token.", | ||
| "hint_url": "https://plugin.example.com/buy", | ||
| "type": "bearer" | ||
| } | ||
| "service": [ | ||
| { | ||
| "id": "#fairpm_repo", | ||
| "serviceEndpoint": "https://repo.example.com/packages/1234", | ||
| "type": "FairPackageManagementRepo" | ||
| }, | ||
| { | ||
| "id": "#fairpm_entitlements", | ||
| "serviceEndpoint": "https://licenses.example.com", | ||
| "type": "FairEntitlementService" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| The FAIR plugin would then display the following UI: | ||
| This is the trust anchor — because you control your DID, you control where entitlement checks go, even if you change repositories. | ||
|
|
||
| ... | ||
|
|
||
| ### 2. Add entitlements to your package metadata | ||
|
|
||
| In your package metadata, specify the `entitlements` property: | ||
|
|
||
| ```json | ||
| { | ||
| "entitlements": { | ||
| "service": "https://licenses.example.com/verify", | ||
| "type": "subscription", | ||
| "hint": "Example Plugin requires an active Pro subscription.", | ||
| "hint_url": "https://example.com/pricing" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The `service` URL must be under the `FairEntitlementService` URL in your DID Document. Clients validate this to prevent rogue repositories from redirecting entitlement checks. | ||
|
|
||
| Available entitlement types: | ||
|
|
||
| | Type | Use case | | ||
| | ------------------- | -------------------------------------------------- | | ||
| | `subscription` | Premium plugins/themes with recurring billing | | ||
| | `purchase` | One-time purchase plugins/themes | | ||
| | `license-key` | Software with traditional license key activation | | ||
| | `free-registration` | Free plugins that require vendor registration | | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider a need a means of forcing a re-check for subscriptions, license expiry, or enforce reauth (oauth) as needed. |
||
| #### When entitlements expire and how often clients re-check | ||
|
|
||
| The protocol is deliberately agnostic about how an entitlement expires. The vendor's entitlement service controls re-verification timing through the `exp` claim on the JWT proof it issues — not through fields in the metadata. This keeps the metadata stable across renewals, plan changes, and authentication-method changes. | ||
|
|
||
| In practice: | ||
|
|
||
| - For a **monthly subscription**, issue proofs valid for a few days to a billing cycle (e.g., 1–30 days). When the subscription renews out of band, the next refresh will simply succeed and the client keeps going. | ||
| - For a **one-time purchase**, issue long-lived proofs (months). Use the `401`/`403` revocation flow if a refund or chargeback occurs. | ||
| - For a **license-key** entitlement, set `exp` to the licence's own expiry. A 365-day licence becomes a proof with `exp = now + 365 days`. | ||
| - For **free-registration**, issue long-lived proofs (weeks to a year). Clients refresh on demand if the vendor revokes. | ||
| - For **high-security plugins** where you want a re-check at least every shift, issue short proofs (e.g., 8 hours). | ||
|
|
||
| Clients are required to cache proofs until `exp` and to refresh on `401`/`403`. This means you do **not** need to perform a fresh entitlement check on every page load — once a client has a valid cached proof, it reuses it until expiry or until the repository rejects it. | ||
|
|
||
| If you need to force clients to skip caching and re-verify on every install/update — for example, for strict per-seat licence enforcement — set `require-reauth: true` on the entitlements object: | ||
|
|
||
| ```json | ||
| { | ||
| "entitlements": { | ||
| "service": "https://licenses.example.com/verify", | ||
| "type": "license-key", | ||
| "require-reauth": true, | ||
| "hint": "Each install verifies your seat allocation in real time.", | ||
| "hint_url": "https://example.com/seats" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Use `require-reauth: true` sparingly: it disables proof caching and adds a verification round-trip to every protected action. | ||
|
|
||
|
|
||
| ### 3. Set up repository authentication | ||
|
|
||
| On each release that has restricted artifacts, set `auth` to tell clients how to authenticate with the repository: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and when reauthentication must occur, if applicable. |
||
|
|
||
| ```json | ||
| { | ||
| "auth": { | ||
| "type": "bearer", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "auth-expiry": "----", |
||
| "hint": "Your entitlement token will be used automatically.", | ||
| "hint_url": "https://example.com/help/installation" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| When a package has both `entitlements` and `auth`, the client flow is: | ||
|
|
||
| 1. Client verifies the user's entitlement with the vendor's service | ||
| 2. The entitlement service returns a signed JWT (entitlement proof) | ||
| 3. Client presents the JWT as a bearer token to the repository | ||
| 4. Repository validates the JWT and serves the artifact | ||
|
|
||
| Mark individual artifacts as restricted using `requires-auth`: | ||
|
|
||
| ```json | ||
| { | ||
| "artifacts": { | ||
| "package": { | ||
| "url": "https://repo.example.com/packages/1234/download/2.1.0", | ||
| "requires-auth": true, | ||
| "signature": "...", | ||
| "checksum": "sha256:..." | ||
| }, | ||
| "banner": { | ||
| "url": "https://repo.example.com/packages/1234/banner.png", | ||
| "content-type": "image/png" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| In this example, the package binary requires authentication (and therefore entitlement verification), but the banner image is publicly accessible. | ||
|
|
||
|
|
||
| ## How it works end-to-end | ||
|
|
||
| When a user wants to install a restricted package: | ||
|
|
||
| 1. **Client resolves the DID** and finds both `FairPackageManagementRepo` and `FairEntitlementService` services. | ||
|
|
||
| 2. **Client fetches metadata** from the repository and sees the `entitlements` property. It validates that the entitlement service URL matches the DID Document. | ||
|
|
||
| 3. **Client displays the requirement** to the user: "This package requires an active Pro subscription. [Learn more](https://example.com/pricing)" | ||
|
|
||
| 4. **User provides credentials** (API key, license key, etc.). | ||
|
|
||
| 5. **Client contacts the entitlement service** with the user's credentials and the package DID. The service verifies the entitlement and returns a signed JWT proof. | ||
|
|
||
| 6. **Client downloads the artifact** from the repository, presenting the JWT as a bearer token. The repository validates the JWT signature and expiration. | ||
|
|
||
| 7. **Client verifies the package signature** against the DID Document's signing keys, as with any package. | ||
|
|
||
|
|
||
| ## Why this separation matters | ||
|
|
||
| Because entitlements are tied to the vendor's DID (not the repository), they survive repository changes. If a vendor moves from Repository A to Repository B: | ||
|
|
||
| - The `FairEntitlementService` in the DID Document stays the same | ||
| - The entitlement service URL in the metadata stays the same | ||
| - Users' entitlements continue to work — the JWT proofs are validated against the vendor's entitlement service, not the repository | ||
| - The new repository just needs to accept the same JWT proofs | ||
|
|
||
| This also means aggregators and caches can enforce the same access controls by validating the same JWTs. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| # FAIR Authentication Methods | ||
|
|
||
| This extension defines the standard authentication methods for the FAIR Package Management Protocol. | ||
|
|
||
| Authentication methods specify **how** a client presents credentials to a repository when accessing artifacts that require authentication (as indicated by the `auth` property on a [Release Document](./specification.md#property-auth) or the `requires-auth` property on an artifact). | ||
|
|
||
| For access control and entitlement verification (determining **whether** a user is authorized), see [Entitlements](./specification.md#entitlements) and [Entitlement Verification](./specification.md#entitlement-verification) in the core specification. | ||
|
|
||
|
|
||
| ## Methods | ||
|
|
||
|
|
||
| ### bearer | ||
|
|
||
| The `bearer` method indicates that the client must present a bearer token in the `Authorization` header. | ||
|
|
||
| ``` | ||
| Authorization: Bearer <token> | ||
| ``` | ||
|
|
||
| The token may be an API key, an [entitlement proof](./specification.md#entitlement-proof) JWT, or any other opaque token accepted by the repository. | ||
|
|
||
| The `auth` object for this method uses only the common properties (`type`, `hint`, `hint_url`). No additional properties are defined. | ||
|
|
||
| Example: | ||
|
|
||
| ```json | ||
| { | ||
| "auth": { | ||
| "type": "bearer", | ||
| "hint": "Enter your repository API key to download this artifact.", | ||
| "hint_url": "https://repo.example.com/account/api-keys" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| ### basic | ||
|
|
||
| The `basic` method indicates that the client must present a username and password using HTTP Basic authentication. | ||
|
|
||
| ``` | ||
| Authorization: Basic <base64(username:password)> | ||
| ``` | ||
|
|
||
| The credentials MUST be encoded as specified in [RFC 7617][rfc7617]. | ||
|
|
||
| The `auth` object for this method uses only the common properties (`type`, `hint`, `hint_url`). No additional properties are defined. | ||
|
|
||
| Example: | ||
|
|
||
| ```json | ||
| { | ||
| "auth": { | ||
| "type": "basic", | ||
| "hint": "Use your repository account credentials.", | ||
| "hint_url": "https://repo.example.com/register" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| [rfc7617]: https://datatracker.ietf.org/doc/html/rfc7617 | ||
|
|
||
|
|
||
| ### oauth2 | ||
|
|
||
| The `oauth2` method indicates that the client must authenticate using an [OAuth 2.0][rfc6749] authorization flow. | ||
|
|
||
| The `auth` object for this method defines the following additional properties: | ||
|
|
||
| * `authorization_url` (required) - A URL string. The OAuth 2.0 authorization endpoint. | ||
| * `token_url` (required) - A URL string. The OAuth 2.0 token endpoint. | ||
| * `scopes` (optional) - A list of strings. The OAuth 2.0 scopes required for access. If omitted, the client SHOULD request no specific scopes. | ||
|
|
||
| Clients SHOULD support the Authorization Code flow as defined in [RFC 6749, Section 4.1][rfc6749-s4.1]. Clients MAY support additional flows as appropriate. | ||
|
|
||
| Example: | ||
|
|
||
| ```json | ||
| { | ||
| "auth": { | ||
| "type": "oauth2", | ||
| "authorization_url": "https://repo.example.com/oauth/authorize", | ||
| "token_url": "https://repo.example.com/oauth/token", | ||
| "scopes": ["packages:read"], | ||
| "hint": "Sign in with your repository account to download.", | ||
| "hint_url": "https://repo.example.com/register" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| [rfc6749]: https://datatracker.ietf.org/doc/html/rfc6749 | ||
| [rfc6749-s4.1]: https://datatracker.ietf.org/doc/html/rfc6749#section-4.1 | ||
|
|
||
|
|
||
| ## Combining Authentication with Entitlements | ||
|
|
||
| When a package has both `entitlements` (on the metadata) and `auth` (on a release), the two work together: | ||
|
|
||
| 1. The client verifies the user's entitlement by contacting the vendor's entitlement service, as described in [Entitlement Verification](./specification.md#entitlement-verification). | ||
| 2. The entitlement service returns an [entitlement proof](./specification.md#entitlement-proof) JWT. | ||
| 3. The client presents the entitlement proof as a bearer token to the repository when downloading artifacts. | ||
|
|
||
| In this flow, the repository's `auth` type is typically `bearer`, and the token is the entitlement proof JWT. The repository validates the JWT signature and expiration before serving the artifact. | ||
|
|
||
| This separation allows: | ||
| - **Vendors** to control who can access their packages (via the entitlement service). | ||
| - **Repositories** to enforce access without needing their own authorization logic (by validating JWTs). | ||
| - **Aggregators and caches** to enforce the same access controls (by accepting the same JWTs). | ||
| - **Users** to move between repositories without losing their entitlements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For type : subscription, should also add
"expires": <date/time as UTC>