Skip to content

Easily authenticate using OAuth 2.0 client/password grants in Dart/Flutter.

License

Notifications You must be signed in to change notification settings

netsells/passputter

Repository files navigation

Passputter

Easily authenticate using OAuth 2.0 client/password grants.

style: very good analysis Gitmoji Pub Version GitHub GitHub Workflow Status Coverage Status

🚀 Installation

Install passputter from pub.dev:

passputter: ^4.0.0

✅ Prerequisites

Passputter is designed to work alongside a REST API which uses OAuth 2.0 Client Credentials and Password Grants, such as Laravel Passport.

You will need:

  • Dio. Passputter only works with Dio.
  • An API endpoint which can be used to request tokens. Passputter will send POST requests to the endpoint containing URL Form Encoded data.
    • Typically these endpoints end with oauth/token
  • A set of OAuth client credentials (an ID and a secret).
    • Some apps have separate credentials for client and password grants. Passputter supports this.

🔨 Usage

📦 Step 1: Implement a way to store tokens

Passputter provides a TokenStorage interface, but you will need to create an implementation. You will need to be able to store, retrieve, and delete client and user tokens. Something secure is strongly recommended such as FlutterSecureStorage, or an encrypted Hive box.

Here's an example of an implementation using Hive:

class HiveTokenStorage implements TokenStorage {
    const HiveTokenStorage(this._box);

    final Box<Map<String, dynamic>> _box;

    static const _clientTokenKey = 'clientToken';
    static const _userTokenKey = 'userToken';

    @override
    FutureOr<OAuthToken?> get clientToken async {
        final tokenMap = _box.get(_clientTokenKey);
        if (tokenMap != null) {
            return OAuthToken.fromJson(tokenMap);
        } else {
            return null;
        }
    }

    @override
    FutureOr<OAuthToken?> get userToken async {
        final tokenMap = _box.get(_userTokenKey);
        if (tokenMap != null) {
            return OAuthToken.fromJson(tokenMap);
        } else {
            return null;
        }
    }

    @override
    Future<void> saveClientToken(OAuthToken token) async {
        await _box.put(_clientTokenKey, token.toJson());
    }

    @override
    Future<void> saveUserToken(OAuthToken token) async {
        await _box.put(_userTokenKey, token.toJson());
    }

    @override
    Future<void> deleteClientToken() async {
        await _box.delete(_clientTokenKey);
    }

    @override
    Future<void> deleteUserToken() async {
        await _box.delete(_userTokenKey);
    }
}

Note: The fromJson and toJson methods in this example aren't actually included in the Passputter package. You could implement them yourself as extension methods.

✨ Step 2: Create a Passputter object

The Passputter class is the class you will use to log the user in and out, as well as to determine the current auth state.

Create one like this:

final passputter = Passputter(
    dio: Dio(), // This will be used to request tokens.
    endpoint: 'https://api.myapp.com/oauth/token',
    clientId: '1', // Client ID for password grant
    clientSecret: 'secret', // Client Secret for password grant
    tokenStorage: tokenStorage, // Your implementation of TokenStorage
);

You can then use your new Passputter instance to log in, log out, and get the current state:

await passputter.isLoggedIn; // false

await passputter.logIn(email: 'email@example.com', password: 'password');

await passputter.isLoggedIn; // true

await passputter.logOut();

await passputter.isLoggedIn; // false

✋🏻 Step 3: Use the token interceptors

Passputter provides two interceptors which you can add to your Dio instances: UserTokenInterceptor and ClientTokenInterceptor.

UserTokenInterceptor will add a user token to all requests. If a token has expired, the interceptor will attempt to refresh it. If no token exists, the interceptor will continue with the request without attaching a token.

ClientTokenInterceptor will add a client token to all requests. If no token is saved in your TokenStorage, the interceptor will attempt to generate one before continuing.

You can retrieve them from your Passputter instance:

dio.interceptors.add(passputter.getClientTokenInterceptor(clientId: '1', clientSecret: 'secret'));
// or
dio.interceptors.add(passputter.userTokenInterceptor);

💰 Step 4: Profit

That's it! You now have a fully working authentication system for your Flutter app.

👨🏻‍💻 Authors