# auth.md

Markdow is a multi-tenant, headless Markdown service. Access is authorized for one signed-in user at a time, and every vault operation verifies that the token subject owns the vault.

## Discover

A protected request without a credential returns:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://markdow.org/.well-known/oauth-protected-resource"
```

Fetch `https://markdow.org/.well-known/oauth-protected-resource` for the [Representational State Transfer (REST)](https://developer.mozilla.org/en-US/docs/Glossary/REST) interface or `https://markdow.org/.well-known/oauth-protected-resource/mcp` for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server. Then fetch `https://markdow.org/.well-known/oauth-authorization-server` for the authorization, token, revocation, and client-registration endpoints.

## Register a public client

Markdow supports dynamic client registration under [Request for Comments 7591](https://www.rfc-editor.org/rfc/rfc7591). Register a public client before beginning the browser flow. No client secret is issued.

```http
POST https://markdow.org/oauth2/register
Content-Type: application/json

{"client_name":"My agent","redirect_uris":["https://client.example/callback"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_endpoint_auth_method":"none"}
```

## Request user authorization

Create a high-entropy verifier and derive an `S256` challenge as defined by [Proof Key for Code Exchange](https://www.rfc-editor.org/rfc/rfc7636). Open this address in the user's browser:

```text
https://markdow.org/oauth2/authorize?response_type=code&client_id=<client_id>&redirect_uri=<registered_redirect_uri>&scope=mcp%20vaults:read%20notes:read&resource=https://markdow.org/mcp&state=<state>&code_challenge=<S256_challenge>&code_challenge_method=S256
```

If the user is not signed in, Markdow asks for their email and sends a one-time sign-in link. Opening the link authenticates that email address. Markdow then shows the requested scopes and requires an explicit confirmation before redirecting to the registered callback with a short-lived authorization code. For the [Model Context Protocol](https://modelcontextprotocol.io/), the `mcp` scope authorizes its published tools for that account; each tool still checks the account and vault it receives.

## Exchange the authorization code

Exchange the code and the original verifier. A public client authenticates this request with its `client_id` and the verifier, not a shared secret.

```http
POST https://markdow.org/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=<client_id>&redirect_uri=<registered_redirect_uri>&code=<authorization_code>&code_verifier=<verifier>&resource=https://markdow.org/mcp
```

The response contains a short-lived access token and, when requested by the registered client, a refresh token. Send `Authorization: Bearer <access_token>` to Markdow. The token is bound to the signed-in user, so it can reach only that user's vaults.

Use `resource=https://markdow.org/mcp` for the Model Context Protocol server and `resource=https://markdow.org` for the REST interface. Binding a token to one resource prevents it from being replayed at the other interface.

## Revoke a token

Revocation follows [Request for Comments 7009](https://www.rfc-editor.org/rfc/rfc7009) and is idempotent:

```http
POST https://markdow.org/oauth2/revoke
Content-Type: application/x-www-form-urlencoded

token=<access_token>&token_type_hint=access_token
```

## Granted scopes

- `users:read`
- `vaults:read`
- `vaults:write`
- `notes:read`
- `notes:write`
- `documents:read`
- `documents:write`
- `embeddings:read`
- `embeddings:write`
- `mcp`

## Service information

- Service: https://markdow.org
- Pricing: self-hosted local mode has no service charge.
- Terms: https://markdow.org/terms
- Privacy: https://markdow.org/privacy

- Integration help: https://github.com/pepicrft/markdow/issues

