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

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

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.0.1  # 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: ^0.18.0  # Replace with actual version
  flutter_web_auth_2: ^4.0.1
  flutter_secure_storage: ^9.2.2

Basic Usage #

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

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 {
      // Get authorization URL for user's handle
      final (authUrl, ctx) = await _client.authorize('shinyakato.dev');

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

      // Handle the OAuth callback
      final session = await _client.callback(result, ctx);

      // 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 {
    // Securely store all session data
    await _storage.write(key: 'access_token', value: session.accessToken);
    await _storage.write(key: 'refresh_token', value: session.refreshToken);
    await _storage.write(key: 'dpop_nonce', value: session.$dPoPNonce);
    await _storage.write(key: 'public_key', value: session.$publicKey);
    await _storage.write(key: 'private_key', value: session.$privateKey);
    await _storage.write(
      key: 'expires_at',
      value: session.expiresAt.toIso8601String(),
    );
  }

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

    return OAuthSession(
      accessToken: accessToken,
      refreshToken: await _storage.read(key: 'refresh_token') ?? '',
      tokenType: 'DPoP',
      expiresAt: DateTime.parse(
        await _storage.read(key: 'expires_at') ?? '',
      ),
      $dPoPNonce: await _storage.read(key: 'dpop_nonce') ?? '',
      $publicKey: await _storage.read(key: 'public_key') ?? '',
      $privateKey: await _storage.read(key: 'private_key') ?? '',
    );
  }

  Future<OAuthSession?> _refreshTokenIfNeeded() async {
    final session = await _loadSession();
    if (session == null) return null;

    // Check if token needs refresh (e.g., 5 minutes before expiration)
    if (session.expiresAt.isBefore(DateTime.now().add(Duration(minutes: 5)))) {
      try {
        final newSession = await _client.refresh(session);
        await _saveSession(newSession);
        return newSession;
      } catch (e) {
        // If refresh fails, clear stored session
        await _storage.deleteAll();
        return null;
      }
    }
    return session;
  }

  @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, you can use the session for API requests with bluesky client

Future<void> _makeAuthenticatedRequest() async {
  final session = await _refreshTokenIfNeeded();
  if (session == null) {
    // Handle unauthenticated state
    return;
  }

  final bsky = Bluesky.fromOAuthSession(session);

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

License #

This project is licensed under the MIT License - see the LICENSE file for details.

1
likes
140
points
482
downloads

Publisher

verified publisheratprotodart.com

Weekly Downloads

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

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#atproto #bluesky #oauth

Documentation

API reference

Funding

Consider supporting this project:

github.com

License

BSD-3-Clause (license)

Dependencies

convert, crypto, freezed_annotation, http, json_annotation, pointycastle

More

Packages that depend on atproto_oauth