Skip to content

How to set up an OAuth client for Port OS

View as Markdown

This guide explains how to register an OAuth client for Port OS so that you can make API calls.

Port OS supports three variants of client registration:

  1. manual registration
  2. dynamic client registration
  3. client information metadata documents

After you've registered an OAuth client, you can then setup authorization via Port OS's supported authorization methods.

  1. Website based auth via authorization code and PKCE
  2. Device based auth via code based linking

manual registration

to register a new oauth client go to the oauth client registration page

you will need to configure the registration with the following:

  1. permitted scopes
  2. authorized callbacks

Manual registration details

On the OAuth client page, choose Create client ID, enter a client name and pick an application type. A Web Application is a confidential client: it receives a client secret, shown once, and also lists its authorized JavaScript origins. A Desktop Application or CLI is a public client that uses PKCE and has no secret.

Authorized callbacks are the exact redirect URIs Port OS may send the user back to. Remote callbacks use HTTPS. HTTP is allowed only for localhost or loopback addresses, and local and remote callbacks cannot be mixed in one client. A callback cannot contain a fragment or user information. A Desktop Application or CLI starts with http://localhost.

Permitted scopes are the most this client can ever ask a user to approve. At authorization a request can name a subset, and the user can uncheck any of it. A client registered by hand can hold any published scope, including the flow, secret, principal and access-control scopes that dynamic registration and metadata documents cannot request. Grant only what the application needs. A client created on the page uses the authorization code grant; a client for the device flow is created through the create OAuth client operation.

Discover the OAuth server and choose REST or MCP

This discovery step is shared by every client. The API is https://api.portoperatingsystem.com and the authorization server is https://auth.portoperatingsystem.com. Fetch the API's /.well-known/oauth-authorization-server document anonymously. Read issuer, authorization_endpoint, token_endpoint, registration_endpoint, revocation_endpoint and scopes_supported. Use the returned auth endpoints exactly. The issuer and API resource have different origins; do not use the auth issuer as the API destination or token audience. Port OS exposes OAuth authorization-server metadata here; it does not advertise OpenID Connect discovery or issue OpenID Connect ID tokens.

InterfaceAPI destinationOAuth resource
RESThttps://api.portoperatingsystem.com plus the documented operation pathhttps://api.portoperatingsystem.com
MCPhttps://api.portoperatingsystem.com/mcp, Streamable HTTPThe resource returned by /.well-known/oauth-protected-resource/mcp

An MCP connector can start at https://api.portoperatingsystem.com/mcp and follow its WWW-Authenticate resource_metadata URL. Fetch that public metadata and the authorization server it names. MCP initialization alone does not prove that the account's devices are accessible. Never use a REST token on MCP or an MCP token on REST.

Choose the transport before authorizing. Omitting resource at authorization selects REST. Supply the discovered resource explicitly to select MCP. Code exchange and refresh may omit resource and inherit the grant audience; an explicit resource must stay unchanged. If a REST request fails because you authorized the MCP resource (or the reverse), obtain a new user authorization for the correct resource. Refresh cannot change the token's audience.

Choose one supported client registration method, then follow the same consent and callback steps. If metadata omits registration_endpoint, new DCR registration is unavailable; do not guess a replacement endpoint or silently switch to another client's credentials.

See scopes and resource rules. Authorization-server scopes_supported describes server vocabulary; a particular client's accepted scopes can be narrower.

Carry discovery values into every request

This JavaScript fragment initializes a REST connection. Keep authorizationServer and resource for the consent and token examples; the API base is the protected resource, independently of the discovered auth issuer.

JavaScript
const apiBase = 'https://api.portoperatingsystem.com';
const response = await fetch(apiBase + '/.well-known/oauth-authorization-server');
if (!response.ok) throw new Error('OAuth discovery failed');
const authorizationServer = await response.json();
const resource = apiBase;

For MCP, assign resource from the protected-resource metadata's resource field instead. Keep the auth issuer separate from the API resource. A wrong resource can produce an invalid_target error callback even when client registration succeeded.

Canonical component: Discover the OAuth server and choose REST or MCP.

Register an OAuth client with DCR or CIMD

Start with discovery. DCR and CIMD identify a client; they do not sign in a user or grant device access. Both use the same PKCE consent and callback flow and the same token lifecycle as website based auth. Neither can use the device flow. Register by hand when you need a confidential client or the device flow.

Dynamic client registration

Prefer CIMD where a client supports it. Port OS keeps DCR for clients that already use it.

POST application/json to the discovered registration_endpoint (https://auth.portoperatingsystem.com/oauth/register). Send one JSON document of at most 16 KiB:

JSON
{
  "client_name": "My device assistant",
  "redirect_uris": ["https://your-client.example/oauth/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "endpoint:read message:send"
}

The same request with curl:

Terminal
curl -X POST https://auth.portoperatingsystem.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My device assistant",
    "redirect_uris": ["https://your-client.example/oauth/callback"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "scope": "endpoint:read message:send"
  }'

Provide one to eight exact redirect_uris. Remote callbacks require HTTPS. Local clients may use exact HTTP callbacks on localhost or literal loopback IPs, such as http://127.0.0.1:8765/callback. All callbacks in a registration must be local or all must be remote; these sets cannot be mixed. No fragments or user information are permitted. Start the local callback listener before authorization. The website's OAuth client page enforces this same callback-set policy.

token_endpoint_auth_method must be none: dynamic registration creates public clients that rely on PKCE and never receive a client_secret. Any other value, including leaving it out, is refused with invalid_client_metadata. Grant types are authorization_code, optionally with refresh_token (both by default), and response_types is code. client_name is limited to 200 characters; a registration without one is named "Unverified OAuth client". software_statement is rejected with unapproved_software_statement.

Success is 201 with client_id, client_id_issued_at and accepted metadata. Supported DCR scopes are endpoint:read, endpoint:write, message:read, message:send, group:read, group:manage, subscription:read and subscription:write. The default is endpoint:read. Request only the task's necessary scopes and check the returned scope. By default, scopes outside this list, such as flow, secret, principal and access-control scopes, are available only to clients you register by hand.

The registration fingerprint is SHA-256 of compact canonical JSON with sorted object keys and sorted redirect_uris, grant_types and response_types arrays, plus client_name, token_endpoint_auth_method and the normalized requested scope. The scope is split on spaces, deduplicated and sorted, so scope order does not matter. Defaults are applied before hashing. Array ordering does not create another client. A matching registration returns the original client ID, issue time, scopes and descriptive metadata, and never overwrites the existing client. A different scope is a different fingerprint, so it gets a new client ID; an existing client's scopes are never widened. Descriptive metadata such as software_id does not change the fingerprint. Fingerprinting is deduplication, not proof of callback ownership or vendor identity.

Default quotas are 1,000 new registrations per rolling day and 10,000 retained registrations. Reuse does not consume new-registration quota. A quota failure returns 429 with Retry-After. An operator can disable new registrations (503) without retiring existing grants. Read the registration operation for exact request, response and error schemas.

Client information metadata documents

A client uses the HTTPS URL of its metadata document as its client_id instead of posting a registration request. This is the client ID metadata document (CIMD) that the authorization server metadata advertises as client_id_metadata_document_supported. Any public HTTPS URL is accepted. It has no query, fragment or user information, and it cannot name localhost, a loopback or private address.

Publish a JSON document at that URL:

JSON
{
  "client_id": "https://your-client.example/oauth/client.json",
  "client_name": "My device assistant",
  "redirect_uris": ["https://your-client.example/oauth/callback"],
  "token_endpoint_auth_method": "none"
}

The document must answer with HTTP 200, stay under 64 KiB, repeat the same URL as client_id, and include client_name and at least one redirect URI. Every redirect URI must be an exact HTTPS callback. token_endpoint_auth_method is none (the default, a public PKCE client) or private_key_jwt, which also needs token_endpoint_auth_signing_alg of RS256 or ES256 and an HTTPS, non-loopback jwks_uri. Other methods are refused.

Port OS retrieves and validates the document on every authorization attempt; redirects from the metadata fetch are refused. A CIMD client must send the resource parameter at authorization and at refresh (website based auth). Its grant types are authorization_code and refresh_token. It can request endpoint:read, endpoint:write, message:send, group:read, group:manage, subscription:read and subscription:write; message:read is not granted to CIMD clients. Do not pretend another client's metadata URL belongs to you.

client_name, software_id, software_version and client_uri remain unverified claims. No trusted software-statement issuer is configured. A registration called Muse or Grok does not establish a verified vendor integration or gain additional permissions. Clients you register by hand keep their separately configured client authentication method.

Canonical component: Register an OAuth client with DCR or CIMD.