@@ 6,15 6,6 @@ description: 'Using the LinkTaco API (GraphQL)'
LinkTaco offers an API for our services via [GraphQL](https://graphql.org).
This page documents the traits our GraphQL API.
-# Attribution
-
-Our GraphQL setup is heavily based on the [SourceHut][srht] GraphQL services.
-As such this document is a modified version of the [original SourceHut
-document][srht og].
-
-[srht]: https://sourcehut.org "SourceHut"
-[srht og]: https://man.sr.ht/graphql.md "SourceHut GraphQL"
-
# API Info
The API is accessed via the location `https://api.linktaco.com`.
@@ 243,3 234,12 @@ An additional field is provided by the `version` resolver: `deprecationDate`.
The field, If not null, indicates the date at which a major
version increment is planned. Interested parties may want to monitor this value
and incorporate it into their planning.
+
+# Documentation Attribution
+
+Our GraphQL setup is heavily based on the [SourceHut][srht] GraphQL services.
+As such this document is a modified version of the [original SourceHut
+document][srht og].
+
+[srht]: https://sourcehut.org "SourceHut"
+[srht og]: https://man.sr.ht/graphql.md "SourceHut GraphQL"
@@ 0,0 1,327 @@
+---
+title: OAuth 2.0 via LinkTaco
+description: 'Using OAuth 2.0 with LinkTaco API'
+---
+
+LinkTaco offers an OAuth 2.0-compatible ([RFC 6749][RFC 6749]) authorization
+process for access to the GraphQL API. This document explains the
+specific details of our RFC 6749 implementation and is intended to be
+accompanied by a reading of the RFC.
+
+[RFC 6749]: https://tools.ietf.org/html/rfc6749
+
+## Constraints
+
+Our OAuth 2.0 implementation has the following caveats:
+
+- Only confidential clients are supported; public clients are not allowed
+
+In the use-case of a native application, where the public client type specified
+in RFC 6749 is preferred, the application must either provide a web server
+component which completes the confidential authentication process and shares
+the access token with the application; or the application should ask the user
+to provide a personal access token, which can be generated at
+[linktaco.com/oauth2/personal][oauth personal]. In the latter case, the
+application should inform the user of the grant string which supports only the
+minimum access level required to use the application's features.
+
+- The implicit grant (section 4.2) is not supported for the same reasons
+- The resource owner password credentials grant (section 4.3) is not supported
+- The client credentials grant (section 4.4) is not used
+
+[oauth personal]: https://linktaco.com/oauth2/personal
+
+## Access scopes
+
+OAuth 2.0 clients using LinkTaco must specify explicitly the list of features they
+wish to be granted permission to use.
+
+The format of the scope string, per [section 3.3][RFC 6749:3.3] is a
+space-delineated list of grants. Each grant has three components: service,
+scope, and access kind; and is formatted as `scope:access kind`.
+
+[RFC 6749:3.3]: https://tools.ietf.org/html/rfc6749#section-3.3
+
+The scope is the specific feature of that service which the grant applies to,
+such as "PROFILE". The list of access scopes available for each API is
+documented in the GraphQL schema for that API in the `enum AccessScope` type.
+
+The access kind is either `RO` or `RW`; respectively referring to read-only or
+read/write access to that scope.
+
+Example: `PROFILE:RO LINKS:RW ANALYTICS:RO`
+
+The scopes necessary to use each GraphQL resolver are also indicated in the
+GraphQL schema with the `@access` directive.
+
+The access scopes supported by LinkTaco, and the required scopes to utilize
+each resolver, are documented in the [GraphQL schema][gql schema].
+
+[gql schema]: https://git.code.netlandish.com/~netlandish/links/tree/master/item/api/graph/schema.graphqls
+
+They can also be requested programmatically by fetching the following URL:
+
+https://api.linktaco.com/query/api-scopes.json
+
+The following scopes are available for use:
+
+- PROFILE
+- LINKS
+- LISTS
+- SHORTS
+- ORGS
+- DOMAINS
+- BILLING
+- ANALYTICS
+- QRCODES
+
+## Client registration
+
+OAuth 2.0 clients may be registered at [linktaco.com/oauth2/clients][oauth
+clients]. You will be issued a client ID and secret per [sections 2.2 &
+2.3][RFC 6749:2.2]. The client ID is a [UUID][RFC 4122], and the secret is
+64-byte random string, [base64][RFC 4648] encoded. It is not necessary to
+interpret them; they are passed verbatim to our OAuth 2.0 endpoints.
+
+[oauth clients]: https://linktaco.com/oauth2/clients
+[RFC 6749:2.2]: https://tools.ietf.org/html/rfc6749#section-2.2
+[RFC 4122]: https://tools.ietf.org/html/rfc4122
+[RFC 4648]: https://tools.ietf.org/html/rfc4648
+
+## Authorization endpoint
+
+The authorization endpoint (see [section 4.1.1][RFC 6749:4.1.1]) is
+`https://linktaco.com/oauth2/authorize`. Note that LinkTaco differs from the
+specification in that it REQUIRES the scope parameter to be provided; per
+[section 3.3][RFC 6749:3.3] meta.sr.ht interprets the absence of the scope
+parameter as an invalid scope and will cause the request to fail.
+
+### The OAuth 2.0 consent page
+
+To start the exchange, direct the user to the following URL:
+
+ https://linktaco.com/oauth2/authorize
+
+Provide the following parameters in the query string:
+
+<dl>
+ <dt>client_id</dt>
+ <dd>The client ID assigned to you in the previous step.</dd>
+ <dt>scope</dt>
+ <dd>A list of scopes you're requesting — see next section.</dd>
+ <dt>response_type</dt>
+ <dd>This **must** be set to `code`.</dd>
+ <dt>state</dt>
+ <dd>Optional: an arbitrary string — see notes.</dd>
+ <dt>redirect_uri</dt>
+ <dd>Optional: your application URI for redirect the user to — see notes.</dd>
+</dl>
+
+The optional `state` field is returned to you after the redirect. One example
+use-case of this field is generating a token before directing the user to the
+authorization page and validating it after the redirect, to prevent users from
+initiating the OAuth flow without you. Most users don't need to worry about
+this.
+
+The optional `redirect_uri` must match the redirect uri specified in the OAuth
+2.0 client configuration.
+
+The authorization code issued is a 32 character hexadecimal string, and it must
+be used within 5 minutes.
+
+[RFC 6749:4.1.1]: https://tools.ietf.org/html/rfc6749#section-4.1.1
+
+## Access token endpoint
+
+The access token endpoint (see [section 4.1.3][RFC 6749:4.1.3]) is
+`https://linktaco.com/oauth2/access-token`. The optional `redirect_uri` must
+match the redirect uri specified in the OAuth 2.0 client configuration. HTTP
+Basic authentication is also recommended per [section 2.3.1][RFC 6749:2.3.1].
+Our access token response will always set the token type to "bearer".
+
+[RFC 6749:4.1.3]: https://tools.ietf.org/html/rfc6749#section-4.1.3
+[RFC 6749:2.3.1]: https://tools.ietf.org/html/rfc6749#section-2.3.1
+
+# Supported authentication profiles
+
+There are various ways, in order of complexity and flexibility, of
+authenticating with the API. Your goal is to obtain an **access token**, which
+can be used to authenticate with the API.
+
+## Personal Access Tokens
+
+The easiest and fastest way to authenticate with LinkTaco is to [create a personal
+access token][oauth personal]. This token will have
+unrestricted access to the API and can be used like a normal access token
+to authenticate API requests (see [Authenticating API
+requests](#authenticating-api-requests)).
+
+**Warning**: do not give your personal access tokens to third parties. Any third
+party which encourages you to give them a personal access token should instead
+develop an OAuth 2.0 client as described in the next section.
+
+## OAuth 2.0 Clients
+
+Personal access tokens are suitable for authenticating yourself, but if you
+intend to provide software that other LinkTaco users can log into, you should
+[create an OAuth client][oauth clients] instead. The OAuth 2.0
+flow allows end-users to grant your client only the privileges it requires to
+execute its duties and to revoke this access at any time. This is accomplished
+through an **OAuth 2.0 exchange**:
+
+1. Direct the user (in a web browser) to a special page on LinkTaco where we
+ present them with the permissions you're asking for and they provide
+ consent.
+2. The user is redirected back to your application's **configured redirect
+ URI**. We will add an **exchange token** to the query string via the `code`
+ parameter.
+3. Your application sends an HTTP request directly to LinkTaco using this
+ exchange token and your client credentials to obtain an access token.
+
+To this end, you need to have an HTTP server running somewhere (`localhost` is
+suitable for testing) that the user can be redirected to upon consenting to the
+permissions you requested. Decide on a URL we can redirect the user to in your
+application, and fill out the base redirect URI accordingly.
+
+Upon submitting the form, your **client ID** and **client secret** will be shown
+to you. **Record your client secret now**. It will not be shown to you again.
+
+### OAuth 2.0 scopes
+
+API endpoints on LinkTaco generally require an access token valid for a specific
+*scope* to be used. This allows you to only request access to the subset of the
+API that you require. Scopes are written with the following notation(s):
+
+ name
+ name:access
+
+`name` is the name of the OAuth 2.0 scope, which is documented alongside the API
+resolvers which require it (e.g. `getFeed` requires the `PROFILE`
+scope). `access` is either `RO` or `RW`, and may be omitted (default:
+`RO`).
+
+Scopes sent to the authorize endpoint should be space separated. For instance:
+
+`PROFILE:RO LINKS:RW ANALYTICS`
+
+### The application redirect
+
+Once the user consents, they will be redirected to your configured application
+`redirect_uri`. We will update your redirect URI with some additional query
+string parameters you can use for the next steps:
+
+<dl>
+ <dt>code</dt>
+ <dd>An exchange token you can use to obtain an access token in the next step.</dd>
+ <dt>iss</dt>
+ <dd>The base URI for the api server. See [RFC 2907][].</dd>
+ <dt>state</dt>
+ <dd>If present, it will be the "state" value given by your application.</dd>
+ <dt>error</dt>
+ <dd>If present, indicates that an error occurred in the process — see notes.</dd>
+ <dt>error_description</dt>
+ <dd>If present, a human friendly error string, if that human is an engineer.</dd>
+ <dt>error_uri</dt>
+ <dd>If present, a URL to a webpage with more details for the given error.</dd>
+</dl>
+
+Possible values for `error`:
+
+- `access_declined`
+- `invalid_request`
+- `invalid_scope`
+- `server_error`
+
+**Important**: the user is able to **edit** the scopes you've requested before
+consenting. You must handle the case where the scopes returned when obtaining
+an access token do not match the scopes requested.
+
+[RFC 9207]: https://datatracker.ietf.org/doc/html/rfc9207
+
+### Obtaining an access token
+
+Once your application retrieves the exchange token from the redirect, you can
+submit an HTTP POST request to `https://linktaco.com/oauth2/access-token`. The
+request `Content-Type` should be `application/x-www-form-urlencoded` and
+include the following parameters:
+
+<dl>
+ <dt>client_id</dt>
+ <dd>The client ID assigned to you when you registered the application.</dd>
+ <dt>client_secret</dt>
+ <dd>The client secret assigned to you when you registered the application.</dd>
+ <dt>grant_type</dt>
+ <dd>The request type being requested. Must be `authorization_code` or
+ `refresh_token`.</dd>
+ <dt>code</dt>
+ <dd>The exchange token issued in the previous step.</dd>
+ <dt>refresh_token</dt>
+ <dd>Only required if the `grant_type` is set to `refresh_token`.</dd>
+</dl>
+
+You will receive a response like this:
+
+```json
+{
+ "access_token": "your access token",
+ "token_type": "bearer",
+ "expires": 1736201028,
+ "scope": "PROFILE:RO ANALYTICS:RW",
+ "refresh_token": "refresh token"
+}
+```
+
+The `expires` date is in Unix epoch format.
+
+You can now use this token for [Authenticating API
+requests](#authenticating-api-requests).
+
+# Authenticating API requests
+
+Authenticating your API request is simple once you have an access token. You
+just need to set the `Authorization` header to `Bearer your-access-token`. For
+example:
+
+```sh
+oauth_token=your oauth token
+curl \
+ --oauth2-bearer "$oauth_token" \
+ -H 'Content-Type: application/json' \
+ -d '{"query": "{ version { major, minor, patch } }"}' \
+ https://api.linktaco.com/query
+```
+
+## Refreshing access tokens
+
+You can refresh an access token programmatically to avoid service disruptions.
+To refresh a token simply follow the same steps in "Obtaining an access token"
+above. You must:
+
+- Specify `refresh_token` in the `grant_type` parameter.
+- Give the access token's refresh token via the `refresh_token` parameter.
+
+Upon a successful refresh you will receive the same response json as when
+obtaining a new token. You should then update your own database with the new
+access token, refresh token, and expiration date.
+
+## Rotating your client secret
+
+On the security tab of your OAuth 2.0 client's dashboard (which can be accessed
+from the [OAuth clients page][oauth clients]), you can rotate your client
+secret, in the event that it is compromised.
+
+## Revoking access tokens
+
+On the security tab of your OAuth 2.0 client's dashboard (which can be accessed
+from the [OAuth clients page][oauth clients]), you can revoke all issued access
+tokens at once, in the event some or all of them are compromised. Users will
+have to repeat the authorization flow.
+
+# Documentation Attribution
+
+Our GraphQL setup is heavily based on the [SourceHut][srht] GraphQL services.
+As such this document is a modified version of the [original SourceHut
+document][srht og].
+
+[srht]: https://sourcehut.org "SourceHut"
+[srht og]: https://man.sr.ht/meta.sr.ht/oauth.md