Authorization for api.blog.service.m7.org

The M7 Blog Service uses M7 access tokens for protected management requests. After validating a token, the service resolves it to a local principal and applies principal status, publication membership, role, and capability rules. Public read requests do not require a credential.

The API base URL is:

https://api.blog.service.m7.org/v1

Supported credential

Credential Format Public transport Where it applies
M7 access token Signed compact JWT; clients must treat it as opaque Authorization: Bearer ACCESS_TOKEN Protected publication-management routes under /v1

The same access-token format is used for user-authorized and confidential machine callers. The token's resolved principal and the selected publication determine what the caller may do.

ID tokens, refresh tokens, authorization codes, device codes, client secrets, and DPoP proofs do not replace an access token on Blog Service routes. Send those credentials only to the M7 SSO endpoint or flow that defines them.

Obtain an access token

The Blog Service does not issue, refresh, or revoke M7 credentials. Discover the current authorization endpoints at:

GET https://sso.user.m7.org/.well-known/openid-configuration

Use the M7 SSO API flow that matches the caller:

Caller Flow
Browser, native application, or server-rendered application acting for a user Authorization code with PKCE
CLI, TV, or other input-constrained application acting for a user Device authorization
Confidential service acting as itself client_credentials

Token exchanges use:

POST https://sso.user.m7.org/token
Content-Type: application/x-www-form-urlencoded

Authenticate the OAuth client using its configured token-endpoint method. Public clients may use none with a body client_id; confidential clients may use methods such as client_secret_basic, client_secret_post, client_secret_jwt, or private_key_jwt. Never embed a confidential client credential in browser, native, CLI, or device code.

User-authorized flows may return a refresh package. Send the refresh_token, binding_chain, binding_link, and any required fingerprint only to the M7 SSO token endpoint. A client_credentials token has no refresh token; obtain a replacement by authenticating the confidential client again.

Send an authenticated request

Use the Bearer scheme, one space, and the access token:

curl --fail-with-body --silent --show-error \
  --request POST 'https://api.blog.service.m7.org/v1/publication/list' \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{}'

Do not put an access token in a URL, query string, request body, or cookie. Do not rely on compatibility token headers. Never log the complete token or Authorization header.

M7 access tokens are JWTs, but decoded claims are not proof that a token is valid or that the caller is authorized for a publication. Use the API response as the authorization decision.

The service currently validates:

  • compact JWT structure and signature;
  • the RS512 signing algorithm;
  • the exact issuer id.m7.org;
  • an HTTPS signing-certificate source hosted by id.m7.org; and
  • nbf, iat, and exp time claims when present.

The general protected-route validator does not currently require a particular aud claim. Clients must still request a token intended for this API and must not reuse credentials across services merely because a local audience check is absent. The production-import route applies its own exact audience check as described below.

The general protected routes also do not authorize from an OAuth scope claim. The current authorization boundary is the active local principal plus the service-managed publication membership and capability set.

Principals and publication boundaries

After token validation, the service resolves the token identity to a local principal. A newly observed supported identity can be registered as an active local principal. Later requests use the current stored principal status and role, so a signed token does not override a disabled or inactive principal.

Protected public routes accept active principals with the route role user, admin, or root. Authorization then follows the resolved principal type:

Principal Publication boundary
User principal Must have an active membership in the selected publication. The membership role supplies the publication capabilities.
Delegate principal Resolves as its own principal and must have its own active publication membership. Its owner link does not substitute for membership.
admin or root principal Is treated as internal and can operate across publications without a membership.
Application or other internal principal Is treated as internal and can operate across publications without a membership. Use this only for trusted service clients.

A user or delegate does not gain access by knowing a publication slug, a publication UUID, or a resource UUID. The selected publication must be active unless the requested lifecycle action explicitly supports archived publications.

Publication membership roles

Only an active membership is used. Invited, disabled, and removed memberships do not authorize a request.

Membership role Current capability boundary
owner or admin Full publication-management capability set, including settings, domains, imports, exports, and publication lifecycle actions.
editor Editorial content, taxonomy, media, redirects, domains listing, and revision operations; excludes owner-only settings and publication lifecycle actions.
moderator or author Basic publication and post-read management capabilities unless a route defines a narrower rule.

Each protected operation also checks its named publication capability. A valid token and active membership can therefore still receive HTTP 403 when the membership role does not include that action.

Any active resolved principal can currently create a publication. The creator becomes its active owner; caller-supplied owner or member overrides are rejected.

Confidential machine callers

Use client_credentials only for a registered confidential application acting as itself. Authenticate the client at the M7 SSO token endpoint using its configured method and request the audience assigned to the integration. The returned application-principal access token is sent to this API with the same Authorization: Bearer ACCESS_TOKEN contract.

Application principals are treated as internal by the current Blog Service policy and receive the full publication capability set without a membership. Provision these clients only when service-wide access is intended, keep their credentials in a server-side secret store, and request short access-token lifetimes. A machine-owner proxy is the exception: it resolves to its owner user principal and remains subject to that principal's publication access.

Production import profile

POST /v1/import/production has additional checks for the trusted source machine. Its access token must:

  • resolve from the deployment's configured source-machine client ID;
  • contain the same client_id as that configured source machine;
  • identify principal_type as oauth_client;
  • contain machine_owner_proxy: true; and
  • contain the issuer-managed owner-principal binding required by that proxy profile; and
  • contain the deployment's exact production-import audience in aud.

The default production-import audience is:

https://api.blog.service.m7.org/v1/

A deployment can configure another exact audience. The OAuth client must be provisioned so its issued token contains the required machine-owner profile; client input alone cannot grant that trust. The production bundle's source.machine_id must also match the verified token client_id.

For an existing target publication, the resolved proxy principal must have an active membership whose capabilities include import.production. For a new target, the proxy principal must be allowed to create a publication and becomes the new publication's owner.

For example, a client configured for client_secret_basic obtains an access token with the deployment's assigned audience:

curl --fail-with-body --silent --show-error \
  --request POST 'https://sso.user.m7.org/token' \
  --user 'CLIENT_ID:CLIENT_SECRET' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'aud=https://api.blog.service.m7.org/v1/' \
  --data-urlencode 'access_expires=900'

This example does not itself create the required machine-owner trust. Use only the registered source-machine client selected by the deployment.

DPoP status

DPoP is not part of the supported Blog Service authorization contract. The service validates the Bearer access token but does not validate a DPoP proof's signature, HTTP method and URL binding, access-token binding, freshness, unique identifier, or replay state. CORS allowance and parsing of DPoP, HTU, or related headers do not provide end-to-end enforcement.

If an existing SSO credential lineage requires DPoP while acquiring or refreshing a credential, follow that SSO compatibility contract. Do not rely on a DPoP proof to authorize or protect a Blog Service resource request.

Expiry, refresh, and revocation

  • Stop using an access token when its issuer-reported lifetime ends. This API does not extend token lifetimes.
  • Refresh a user-authorized package only at POST https://sso.user.m7.org/token, using the complete package and the OAuth client's required authentication.
  • Reissue a client_credentials token through the same SSO token endpoint; it has no refresh package.
  • Revoke an eligible credential through POST https://sso.user.m7.org/revoke and discard every local copy. The Blog Service has no local token-revocation endpoint.
  • The Blog Service validates access-token JWTs locally and does not perform online introspection for every request. A revoked access token can therefore remain accepted until its signed validity period ends.
  • Disabling the local principal or publication membership can deny later requests independently of token expiry.

After a terminal refresh or revocation error, discard the unusable credential package and begin a new supported authorization flow instead of retrying it indefinitely.

Authentication and authorization failures

Principal-route failures use HTTP 401 when no usable principal is available and HTTP 403 for other principal access denials:

{
  "status": 0,
  "comment": "Access denied",
  "data": {
    "code": 401,
    "reason": "missing_principal",
    "boundary": "principal",
    "stage": "stage-2-mvp-0"
  }
}

Publication membership and capability failures also use the service's normal error envelope. Inspect the HTTP status, status, and data.code; do not branch only on the human-readable comment.

Failure Typical cause Client action
HTTP 401, missing_principal Bearer header is absent, token validation failed, or claims could not resolve a principal Send a valid M7 access token; if one was already sent, discard it and obtain a replacement from SSO.
HTTP 403, principal access denial The local principal is inactive or its route role is not allowed Restore the principal's access or use another authorized principal.
HTTP 403, publication access denial The user or delegate lacks an active membership in the selected publication Use a member principal or request access through the publication owner.
HTTP 403, capability access denial The membership role does not grant the requested operation Use an allowed operation or a membership role with that capability.
HTTP 403, production import access denied The source-machine client, machine-owner marker, or exact audience does not match Stop and correct the registered machine-client configuration; do not retry with caller-supplied identity claims.

Security checklist

  • Use TLS and the exact https://api.blog.service.m7.org/v1 production base URL.
  • Keep access tokens, refresh packages, client secrets, authorization codes, device codes, fingerprints, and DPoP private keys out of source control, logs, analytics, URLs, and error reports.
  • Store long-lived refresh material and confidential-client credentials in an application-owned secure store.
  • Request only the grants, audience, scopes, and lifetime required by the integration.
  • Revoke and replace credentials after suspected disclosure.
  • Treat publication and content identifiers as references, not credentials; the service still applies principal and publication authorization.
  • Use current API responses and server-managed membership state for access decisions. Do not authorize from decoded JWT claims in client code.

Related documentation: