Skip to main content

Authentication

Misskey uses token-based authentication. A secret token (called i in the Misskey API) is injected into every authenticated POST request body. Unlike OAuth-based APIs, there is no Bearer header — the token travels inside the JSON body.

How TokenProvider works

TokenProvider is a typedef for a callback that returns FutureOr<String?>:

typedef TokenProvider = FutureOr<String?> Function();

The callback is invoked for each authenticated request. This design lets you refresh or lazily load tokens without rebuilding the client.

Synchronous token

final client = MisskeyClient(
config: MisskeyClientConfig(
baseUrl: Uri.parse('https://misskey.example.com'),
),
tokenProvider: () => 'your_token_here',
);

Asynchronous token (e.g. loaded from secure storage)

final client = MisskeyClient(
config: MisskeyClientConfig(
baseUrl: Uri.parse('https://misskey.example.com'),
),
tokenProvider: () async {
return await secureStorage.read(key: 'misskey_token');
},
);

Token refresh pattern

If your token can be rotated, return the latest value from the provider. The client always calls the callback immediately before sending, so returning an up-to-date value is enough:

String? _cachedToken;

final client = MisskeyClient(
config: MisskeyClientConfig(
baseUrl: Uri.parse('https://misskey.example.com'),
),
tokenProvider: () => _cachedToken,
);

// Update the cached value whenever your token changes.
void onTokenRefreshed(String newToken) {
_cachedToken = newToken;
}

AuthMode enum

Each API endpoint declares its authentication requirement using the AuthMode enum:

ModeBehavior
AuthMode.requiredToken is injected; throws if none is provided (default)
AuthMode.optionalToken is injected when available; request proceeds without one
AuthMode.noneToken is never injected, regardless of the provider

This is an internal library concern. As a consumer, you only need to supply tokenProvider. The library handles injection automatically.

Endpoints with AuthMode.optional return richer responses when authenticated. For example, notes/show includes myReaction and isFavorited fields only when a token is present.

Obtaining a token: MiAuth flow

Misskey does not use OAuth 2.0. The recommended flow for third-party apps is MiAuth, a simplified browser-redirect flow.

1. Generate a session UUID
2. Open the MiAuth URL in a browser
3. User grants permission on the Misskey web UI
4. Redirect returns to your callback URL (or you poll the API)
5. Exchange the session UUID for a token

Step 1: Build the authorization URL

import 'package:uuid/uuid.dart';

final session = const Uuid().v4();
final baseUrl = 'https://misskey.example.com';

final authUrl = Uri.parse('$baseUrl/miauth/$session').replace(
queryParameters: {
'name': 'My App',
'callback': 'myapp://auth/callback',
'permission': 'read:account,write:notes',
},
);
// Open authUrl in a browser or WebView

Step 2: Exchange the session for a token

After the user approves the request, call the check endpoint. This is a plain HTTP GET and does not go through the library's authenticated path:

final response = await http.get(
Uri.parse('$baseUrl/api/miauth/$session/check'),
);
final json = jsonDecode(response.body) as Map<String, dynamic>;
final token = json['token'] as String?;

Store the token securely and pass it to tokenProvider.

Unauthenticated usage

Pass no tokenProvider to access public endpoints:

final client = MisskeyClient(
config: MisskeyClientConfig(
baseUrl: Uri.parse('https://misskey.example.com'),
),
);

// Works without a token
final info = await client.meta.fetch();
final notes = await client.notes.timelineLocal(limit: 10);

Calling an endpoint with AuthMode.required without a provider throws MisskeyUnauthorizedException.