OAuth2 (Dart)

認証ライブラリOAuth2DartFlutterPKCEAPIセキュリティ

認証ライブラリ

OAuth2 (Dart)

概要

OAuth2 (Dart)は、DartとFlutterアプリケーション向けの公式OAuth2クライアントライブラリです。OAuth2プロトコルを使用してユーザーの代理で認証を行い、認可されたHTTPリクエストを作成するための機能を提供します。Authorization Code Grant、Client Credentials Grant、Resource Owner Password Grantフローをサポートし、サーバーサイドアプリケーション(dart:ioを使用)に対応しています。

詳細

OAuth2 (Dart)は、OAuth2.0仕様(RFC 6749)に準拠したDart言語用の認証ライブラリです。Googleが公式に提供するライブラリであり、Dart 3との互換性を確保しています。主な特徴として、透明なトークン管理とリフレッシュ機能、安全なトークンストレージとキャッシュ、PKCE(Proof Key for Code Exchange)のサポートを提供します。

ライブラリは複数のOAuth2フローに対応しており、Authorization Code Grantは最も安全で推奨されるフロー、Client Credentials Grantはサーバー間通信用、Resource Owner Password Grantは信頼できるクライアント用となっています。特にWebアプリケーションとモバイルアプリケーションの両方での使用に適しており、セキュリティベストプラクティスに従った実装となっています。

メリット・デメリット

メリット

  • 公式サポート: Googleが公式に提供・保守するライブラリで安定性が高い
  • Dart 3対応: 最新のDart言語仕様との完全な互換性
  • 包括的なフロー対応: 主要なOAuth2フローを網羅的にサポート
  • 透明なトークン管理: アクセストークンとリフレッシュトークンの自動管理
  • PKCEサポート: モダンなセキュリティ標準への対応
  • サーバーサイド対応: dart:ioを使用したサーバーサイドアプリケーションに適用可能

デメリット

  • サーバーサイド限定: ブラウザ環境での直接使用は制限される
  • 設定の複雑さ: 初期設定とプロバイダーごとの設定が複雑になる場合がある
  • 依存関係: HTTP通信ライブラリなどの外部依存関係が必要
  • ドキュメント: 他の言語に比べて日本語リソースが限定的

参考ページ

書き方の例

基本的な認証フロー

import 'package:oauth2/oauth2.dart' as oauth2;

// OAuth2クライアントの設定
var authorizationEndpoint = Uri.parse('https://example.com/oauth/authorize');
var tokenEndpoint = Uri.parse('https://example.com/oauth/token');
var identifier = 'your-client-id';
var secret = 'your-client-secret';
var redirectUrl = Uri.parse('https://your-app.com/callback');

// Authorization Code Grantフローを使用
var grant = oauth2.AuthorizationCodeGrant(
  identifier,
  authorizationEndpoint,
  tokenEndpoint,
  secret: secret,
);

// 認証URLの取得
var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
print('認証URL: $authorizationUrl');

// コールバック処理後のクライアント取得
var client = await grant.handleAuthorizationResponse(queryParameters);

リフレッシュトークンを使った認証

import 'package:oauth2/oauth2.dart' as oauth2;

// 保存されたクレデンシャルからクライアントを復元
var credentialsJson = getStoredCredentials(); // 何らかの方法で取得
var credentials = oauth2.Credentials.fromJson(credentialsJson);

var client = oauth2.Client(
  credentials,
  identifier: 'your-client-id',
  secret: 'your-client-secret',
);

// 自動的にトークンをリフレッシュして認証済みリクエストを実行
var response = await client.get(Uri.parse('https://api.example.com/user'));

PKCEを使った認証

import 'package:oauth2/oauth2.dart' as oauth2;
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'dart:math';

// PKCEチャレンジの生成
String generateCodeVerifier() {
  var random = Random.secure();
  var values = List<int>.generate(32, (i) => random.nextInt(256));
  return base64UrlEncode(values).replaceAll('=', '');
}

String generateCodeChallenge(String verifier) {
  var bytes = utf8.encode(verifier);
  var digest = sha256.convert(bytes);
  return base64UrlEncode(digest.bytes).replaceAll('=', '');
}

// PKCE付きの認証フロー
var codeVerifier = generateCodeVerifier();
var codeChallenge = generateCodeChallenge(codeVerifier);

var grant = oauth2.AuthorizationCodeGrant(
  identifier,
  authorizationEndpoint,
  tokenEndpoint,
  codeChallenge: codeChallenge,
  codeChallengeMethod: 'S256',
);

スコープ指定での認証

import 'package:oauth2/oauth2.dart' as oauth2;

// スコープを指定した認証
var scopes = ['read', 'write', 'admin'];

var authorizationUrl = grant.getAuthorizationUrl(
  redirectUrl,
  scopes: scopes,
);

print('認証URL(スコープ付き): $authorizationUrl');

エラーハンドリング

import 'package:oauth2/oauth2.dart' as oauth2;

try {
  var client = await grant.handleAuthorizationCode('authorization-code');
  
  // 認証済みAPIリクエスト
  var response = await client.get(Uri.parse('https://api.example.com/data'));
  
  if (response.statusCode == 200) {
    print('APIレスポンス: ${response.body}');
  }
} on oauth2.AuthorizationException catch (e) {
  print('認証エラー: ${e.error}');
  print('詳細: ${e.description}');
} on oauth2.ExpirationException catch (e) {
  print('トークン期限切れ: ${e.message}');
  // リフレッシュトークンを使って再認証
} catch (e) {
  print('その他のエラー: $e');
}

クレデンシャルの永続化

import 'package:oauth2/oauth2.dart' as oauth2;
import 'dart:convert';
import 'dart:io';

// クレデンシャルの保存
void saveCredentials(oauth2.Credentials credentials) {
  var credentialsJson = credentials.toJson();
  var file = File('credentials.json');
  file.writeAsStringSync(jsonEncode(credentialsJson));
}

// クレデンシャルの読み込み
oauth2.Credentials? loadCredentials() {
  try {
    var file = File('credentials.json');
    if (file.existsSync()) {
      var credentialsMap = jsonDecode(file.readAsStringSync());
      return oauth2.Credentials.fromJson(credentialsMap);
    }
  } catch (e) {
    print('クレデンシャル読み込みエラー: $e');
  }
  return null;
}

// 保存されたクレデンシャルを使ったクライアント初期化
oauth2.Client? createClientFromStorage() {
  var credentials = loadCredentials();
  if (credentials != null) {
    return oauth2.Client(
      credentials,
      identifier: 'your-client-id',
      secret: 'your-client-secret',
    );
  }
  return null;
}