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
| Scope | Access |
|---|---|
read | Read articles, profile, series, analytics |
write | Create and update drafts and articles |
analytics | Access detailed analytics |
newsletter | Manage 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
| Error | Description |
|---|---|
invalid_request | Missing or malformed parameters |
invalid_client | Unknown or inactive client |
invalid_grant | Code expired, used, PKCE mismatch, or redirect_uri mismatch |
unsupported_grant_type | Only authorization_code and refresh_token are supported |
invalid_client_metadata | Malformed registration request |