Misar IO Docs
Misar.BlogApi Reference

OAuth 2.0

Use OAuth 2.0 with PKCE to let third-party apps access MisarBlog on behalf of users.

Overview

MisarBlog supports OAuth 2.0 Authorization Code flow with PKCE (RFC 7636) for third-party integrations. OAuth tokens let external apps read and write blog content without exposing a user's API key.

Who needs OAuth?

Use OAuth if you're building an app that accesses other users' MisarBlog accounts. If you're accessing your own account (scripts, automation, integrations), use an API key instead — it's simpler.


Authorization server metadata

GET /.well-known/oauth-authorization-server

Returns RFC 8414 metadata including all endpoint URLs, supported scopes, and PKCE methods.

{
  "issuer": "https://www.misar.blog",
  "authorization_endpoint": "https://www.misar.blog/api/oauth/authorize",
  "token_endpoint": "https://www.misar.blog/api/oauth/token",
  "revocation_endpoint": "https://www.misar.blog/api/oauth/revoke",
  "registration_endpoint": "https://www.misar.blog/api/oauth/register",
  "scopes_supported": ["read", "write", "analytics", "newsletter"],
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"]
}

Dynamic client registration

Register your OAuth client programmatically (RFC 7591). Requires an API key.

POST api.misar.io/blog/oauth/register
Authorization: Bearer mbk_your_api_key

Request

{
  "client_name": "My Blog App",
  "redirect_uris": ["https://myapp.com/oauth/callback"],
  "scopes": "read write"
}

Response

{
  "client_id": "blog_abc123def456...",
  "client_secret": "your-client-secret-shown-once",
  "client_name": "My Blog App",
  "redirect_uris": ["https://myapp.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "client_secret_post"
}

Save client_secret immediately — it is shown only once and cannot be retrieved later.


Authorization flow (PKCE)

Step 1 — Generate PKCE values

function generatePKCE() {
  const verifier = crypto.randomUUID().replace(/-/g, "") + crypto.randomUUID().replace(/-/g, "");
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  return crypto.subtle.digest("SHA-256", data).then((hash) => {
    const base64url = btoa(String.fromCharCode(...new Uint8Array(hash)))
      .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
    return { verifier, challenge: base64url };
  });
}

Step 2 — Redirect to authorization endpoint

GET https://www.misar.blog/api/oauth/authorize
  ?client_id=blog_abc123
  &redirect_uri=https://myapp.com/oauth/callback
  &response_type=code
  &scope=read+write
  &state=random-state-value
  &code_challenge=<SHA-256 base64url of verifier>
  &code_challenge_method=S256

The user logs in (if not already) and is redirected to your redirect_uri:

https://myapp.com/oauth/callback?code=auth-code&state=random-state-value

Step 3 — Exchange code for tokens

POST https://www.misar.blog/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=auth-code
&redirect_uri=https://myapp.com/oauth/callback
&client_id=blog_abc123
&client_secret=your-client-secret
&code_verifier=your-original-verifier

Response

{
  "access_token": "access-token-value",
  "refresh_token": "refresh-token-value",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read write"
}

Refresh tokens

Access tokens expire after 1 hour. Use the refresh token to get a new pair. Refresh tokens rotate — each use invalidates the old token.

POST https://www.misar.blog/api/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=your-refresh-token
&client_id=blog_abc123

Response

{
  "access_token": "new-access-token",
  "refresh_token": "new-refresh-token",
  "token_type": "Bearer",
  "expires_in": 3600
}

Token revocation

Revoke an access or refresh token when a user logs out (RFC 7009). Always returns 200 OK.

POST https://www.misar.blog/api/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=token-to-revoke

Scopes

ScopeAccess
readRead articles, profile, series, analytics
writeCreate and update drafts and articles
analyticsAccess detailed analytics
newsletterManage newsletter subscribers and issues

Using the access token

Pass the access token in the Authorization header on all API requests:

const res = await fetch("https://api.misar.io/blog/v1/articles", {
  headers: { Authorization: `Bearer ${accessToken}` },
});

Error codes

ErrorDescription
invalid_requestMissing or malformed parameters
invalid_clientUnknown or inactive client
invalid_grantCode expired, used, PKCE mismatch, or redirect_uri mismatch
unsupported_grant_typeOnly authorization_code and refresh_token are supported
invalid_client_metadataMalformed registration request