atproto_oauth 0.5.0 copy "atproto_oauth: ^0.5.0" to clipboard
atproto_oauth: ^0.5.0 copied to clipboard

Provides tools to handle OAuth for AT Protocol and Bluesky Social.

atproto_oauth

AT Protocol OAuth Authentication for Flutter #

This guide explains how to implement AT Protocol OAuth authentication in your Flutter application using FlutterWebAuth2 for services like Bluesky.

Client Metadata #

See [AT Protocol instruction] about client metadata.

Installation #

Add the following dependencies to your pubspec.yaml:

dependencies:
  atproto_oauth: ^0.5.0  # Replace with actual version
  flutter_web_auth_2: ^4.0.1
  flutter_secure_storage: ^9.2.2

Or if you would like to use this feature on Bluesky:

dependencies:
  bluesky: ^2.0.0  # Replace with actual version
  flutter_web_auth_2: ^4.0.1
  flutter_secure_storage: ^9.2.2

Note: AT Protocol OAuth tokens are opaque — this library never decodes them as JWTs. Every stage of the flow is pluggable: inject your own OAuthStateStore, OAuthSessionStore, DPoPNonceCache, IdentityResolver, or DPoPSigner into OAuthClient (each has an in-memory / HTTP / pointycastle default). The example below manages session persistence explicitly instead.

Basic Usage #

Here's how to implement AT Protocol OAuth authentication in your Flutter app:

import 'dart:convert';

import 'package:atproto_oauth/atproto_oauth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class BlueskyAuth extends StatefulWidget {
  @override
  _BlueskyAuthState createState() => _BlueskyAuthState();
}

class _BlueskyAuthState extends State<BlueskyAuth> {
  late OAuthClient _client;
  final _storage = const FlutterSecureStorage();

  @override
  void initState() {
    super.initState();
    _initializeOAuth();
  }

  Future<void> _initializeOAuth() async {
    // Initialize OAuth client with metadata
    // Replace with your client metadata
    final metadata = await getClientMetadata(
      'https://atprotodart.com/oauth/bluesky/atprotodart/client-metadata.json'
    );
    _client = OAuthClient(metadata);
  }

  Future<void> _startAuth() async {
    try {
      // Resolve the account and get the authorization URL. The per-request
      // context is stored inside the client's OAuthStateStore (keyed by the
      // `state` value), so `callback` below does not need it passed back.
      final authUrl = await _client.authorize('shinyakato.dev');

      // Launch OAuth flow in browser
      final result = await FlutterWebAuth2.authenticate(
        url: authUrl.toString(),
        callbackUrlScheme: 'your-app-scheme',
      );

      // Handle the OAuth callback (single argument)
      final session = await _client.callback(result);

      // Store the session securely
      await _saveSession(session);

      // Show success message
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Successfully logged in!')),
      );
    } catch (e) {
      // Handle errors
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Authentication failed: $e')),
      );
    }
  }

  Future<void> _saveSession(OAuthSession session) async {
    // OAuthSession serializes to/from JSON. It contains the DPoP private key
    // and the access/refresh tokens, so only persist it into secure,
    // access-controlled storage — never plaintext.
    await _storage.write(
      key: 'oauth_session',
      value: jsonEncode(session.toJson()),
    );
  }

  Future<OAuthSession?> _loadSession() async {
    final raw = await _storage.read(key: 'oauth_session');
    if (raw == null) return null;

    // Sessions saved by atproto_oauth <= 0.4.x used a different shape; restore
    // those with `OAuthSession.fromLegacyJson(json, issuer: ..., pds: ...)`.
    return OAuthSession.fromJson(jsonDecode(raw) as Map<String, dynamic>);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: _startAuth,
          child: Text('Login with Bluesky'),
        ),
      ),
    );
  }
}

Platform Configuration #

See docs on flutter_web_auth_2.

Using Bluesky Client #

Once authenticated, wrap the session in an OAuthSessionManager and hand it to the bluesky client. The manager builds the DPoP proof for every request and refreshes the access token automatically — proactively just before it expires and reactively on a 401 — so you no longer refresh by hand. Listen to onSessionUpdated to persist the rotated session.

Future<Bluesky?> _authenticatedBluesky() async {
  final session = await _loadSession();
  if (session == null) {
    // Handle unauthenticated state
    return null;
  }

  // Pass the client so the manager can refresh; persist rotations.
  final manager = OAuthSessionManager.fromSession(session, client: _client);
  manager.onSessionUpdated.listen(_saveSession);

  return Bluesky.fromOAuth(manager);
}

Future<void> _makeAuthenticatedRequest() async {
  final bsky = await _authenticatedBluesky();
  if (bsky == null) return;

  // Anyway you want it    !
  final record = await bsky.feed.post.create(text: 'Nice DPoP proof');
}

If a refresh fails because the session was revoked, the manager surfaces an OAuthSessionRevokedException and clears it from the session store — route the user back through authorize in that case.

1
likes
0
points
3.2k
downloads

Publisher

verified publisheratprotodart.com

Weekly Downloads

Provides tools to handle OAuth for AT Protocol and Bluesky Social.

Homepage
Repository (GitHub)
View/report issues

Topics

#atproto #bluesky #oauth

Funding

Consider supporting this project:

github.com

License

unknown (license)

Dependencies

atproto_identity, convert, crypto, freezed_annotation, http, json_annotation, pointycastle

More

Packages that depend on atproto_oauth