OAuth2 (Dart)

Authentication LibraryOAuth2DartFlutterPKCEAPISecurity

Authentication Library

OAuth2 (Dart)

Overview

OAuth2 (Dart) is the official OAuth2 client library for Dart and Flutter applications. It provides functionality for authenticating on behalf of a user using the OAuth2 protocol and making authorized HTTP requests with the user's OAuth2 credentials. It supports Authorization Code Grant, Client Credentials Grant, and Resource Owner Password Grant flows, compatible with server-side applications using dart:io.

Details

OAuth2 (Dart) is an authentication library for the Dart language that complies with the OAuth2.0 specification (RFC 6749). Officially provided by Google, this library ensures compatibility with Dart 3. Key features include transparent token management and refresh capabilities, secure token storage and caching, and support for PKCE (Proof Key for Code Exchange).

The library supports multiple OAuth2 flows: Authorization Code Grant is the most secure and recommended flow, Client Credentials Grant is for server-to-server communication, and Resource Owner Password Grant is for trusted clients. It's particularly suitable for use in both web applications and mobile applications, implementing security best practices.

Pros and Cons

Pros

  • Official Support: Officially provided and maintained by Google with high stability
  • Dart 3 Compatible: Full compatibility with the latest Dart language specifications
  • Comprehensive Flow Support: Covers all major OAuth2 flows comprehensively
  • Transparent Token Management: Automatic management of access tokens and refresh tokens
  • PKCE Support: Compliance with modern security standards
  • Server-side Ready: Applicable to server-side applications using dart:io

Cons

  • Server-side Limited: Direct use in browser environments is restricted
  • Configuration Complexity: Initial setup and provider-specific configuration can be complex
  • Dependencies: Requires external dependencies such as HTTP communication libraries
  • Documentation: Limited Japanese resources compared to other languages

Reference Pages

Code Examples

Basic Authentication Flow

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

// OAuth2 client configuration
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');

// Using Authorization Code Grant flow
var grant = oauth2.AuthorizationCodeGrant(
  identifier,
  authorizationEndpoint,
  tokenEndpoint,
  secret: secret,
);

// Get authorization URL
var authorizationUrl = grant.getAuthorizationUrl(redirectUrl);
print('Authorization URL: $authorizationUrl');

// Get client after callback processing
var client = await grant.handleAuthorizationResponse(queryParameters);

Authentication with Refresh Token

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

// Restore client from saved credentials
var credentialsJson = getStoredCredentials(); // Retrieved by some method
var credentials = oauth2.Credentials.fromJson(credentialsJson);

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

// Automatically refresh tokens and make authenticated requests
var response = await client.get(Uri.parse('https://api.example.com/user'));

Authentication with PKCE

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

// Generate PKCE challenge
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('=', '');
}

// Authentication flow with PKCE
var codeVerifier = generateCodeVerifier();
var codeChallenge = generateCodeChallenge(codeVerifier);

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

Authentication with Scopes

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

// Authentication with specified scopes
var scopes = ['read', 'write', 'admin'];

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

print('Authorization URL (with scopes): $authorizationUrl');

Error Handling

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

try {
  var client = await grant.handleAuthorizationCode('authorization-code');
  
  // Authenticated API request
  var response = await client.get(Uri.parse('https://api.example.com/data'));
  
  if (response.statusCode == 200) {
    print('API Response: ${response.body}');
  }
} on oauth2.AuthorizationException catch (e) {
  print('Authorization Error: ${e.error}');
  print('Details: ${e.description}');
} on oauth2.ExpirationException catch (e) {
  print('Token Expired: ${e.message}');
  // Re-authenticate using refresh token
} catch (e) {
  print('Other Error: $e');
}

Credentials Persistence

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

// Save credentials
void saveCredentials(oauth2.Credentials credentials) {
  var credentialsJson = credentials.toJson();
  var file = File('credentials.json');
  file.writeAsStringSync(jsonEncode(credentialsJson));
}

// Load credentials
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('Credentials loading error: $e');
  }
  return null;
}

// Initialize client using saved credentials
oauth2.Client? createClientFromStorage() {
  var credentials = loadCredentials();
  if (credentials != null) {
    return oauth2.Client(
      credentials,
      identifier: 'your-client-id',
      secret: 'your-client-secret',
    );
  }
  return null;
}