Flutter Tutorial

This tutorial demonstrates how to add user login, logout, and profile to a Flutter application.

This tutorial shows how to use PlusAuth with Flutter Application. If you do not have a PlusAuth account, register from here.

This tutorial follows the plusauth-flutter-starter sample project on Github. You can download and follow the tutorial via the sample project.

Create PlusAuth Client

After you sign up or log in to PlusAuth, you need to create a client to get the necessary configuration keys in the dashboard. Go to Clients and create a client with the type of Native Application

Configure Client

Get Client Properties

You will need your Client Id for interacting with PlusAuth. You can retrieve them from the created client's details.

Configure Redirect and Logout URIs

When PlusAuth authenticates a user, it needs a URI to redirect back. That URI must be in your client's Redirect URI list. If your application uses a redirect URI that is not white-listed in your PlusAuth Client, you will receive an error.

The same thing applies to the logout URIs. After the user logs out, you need a URI to be redirected.

If you are following the sample project, the Redirect URL you need to add to the Redirect URIs field is com.plusauth.flutterexample:/oauthredirect/login. The Logout URL you need to add to the Post Logout Redirect URIs field is com.plusauth.flutterexample:/.

Configure Flutter to use PlusAuth

Create a Flutter application or download the sample project from the link on the top of the page.

Add Dependencies

To get started, install the following dependencies.

# installation with flutter pub
flutter pub add flutter_appauth
flutter pub add http
flutter pub add flutter_secure_storage

Android Setup

Add redirect scheme to your Android app module's build.gradle file.

...
android {
    ...
    defaultConfig {
        ...
        manifestPlaceholders += [
                'appAuthRedirectScheme': '<your_custom_scheme>' // ex: com.plusauth.flutterexample
        ]
    }
}

Add the following queries to your AndroidManifest.xml file a level underneath the <manifest> element.

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https" />
    </intent>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.APP_BROWSER" />
        <data android:scheme="https" />
    </intent>
</queries>

iOS Setup

Go to the Info.plist for your iOS app and add a redirect scheme like the following.

<key>CFBundleURLSchemes</key>
<array>
    <string>your_custom_scheme</string> <!-- ex: com.plusauth.flutterexample -->
</array>

Configure FlutterAppAuth Instance

We need to initialize our Flutter AppAuth library to handle authentication-related operations. Create a new FlutterAppAuth instance in main.dart under lib folder.

// lib/main.dart

class _MyHomePageState extends State<MyHomePage> {

  // ...

  final String _clientId = '<YOUR_CLIENT_ID>';
  final String _redirectUrl = 'com.plusauth.flutterexample:/oauthredirect/login';
  final String _issuer = 'https://<YOUR_TENANT_ID>.plusauth.com';
  final String _postLogoutRedirectUrl = 'com.plusauth.flutterexample:/';
  final List<String> _scopes = <String>['openid', 'profile', 'email', 'offline_access'];

// Create FlutterAppAuth instance
  final FlutterAppAuth _appAuth = FlutterAppAuth();

  // Create FlutterSecureStorage instance to store refresh_token securely
  final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();

  // ...
}

Replace _clientId value with your Client Id defined in the Configure Client section. Also change the _issuer value with your Tenant Id.

If you set your custom scheme other than com.plusauth.flutterexample, replace the _redirectUrl and _postLogoutRedirectUrl values accordingly.

Implement login, user profile, and logout

Add the following sections to your main.dart file under the lib folder. We will store the retrieved refresh_token in secure storage to exchange with new tokens.

Add Login

We will be using the authorizeAndExchangeCode method to authenticate the user. The application redirects you to the PlusAuth login page using the browser.

Add the following section to your main.dart file to start an authentication process.

// lib/main.dart

Future<void> _signIn() async {
  try {
    _setLoading(true);
    final AuthorizationTokenResponse? result = await _appAuth.authorizeAndExchangeCode(
      AuthorizationTokenRequest( _clientId, _redirectUrl, issuer: _issuer, scopes: _scopes),
    );
    if (result != null) {
      _processAuthTokenResponse(result); // process and extract tokens from response
    }
  } catch (_) {
    _setLoading(false);
  }
}

After successful authentication, extract the tokens from response data. We will be storing refresh_token in local storage to retrieve new tokens later.

// lib/main.dart

// Process login response
void _processAuthTokenResponse(AuthorizationTokenResponse response) {
  // Save refresh token to storage to exchange with new one
  _secureStorage.write(key: 'refresh_token', value: response.refreshToken);
  setState(() {
    _isLoggedIn = true;
    _accessToken = response.accessToken!;
    _idToken =  response.idToken!;
    _refreshToken = response.refreshToken!;
  });
    _fetchUserInfo();
}

Add Logout

Add the following section to log out from the application and PlusAuth:

// lib/main.dart

Future<void> _singOut() async {
  try {
    _setLoading(true);
    // User Logout
    await _appAuth.endSession(EndSessionRequest(
      idTokenHint: _idToken, postLogoutRedirectUrl: _postLogoutRedirectUrl, issuer: _issuer));
    _clearSessionInfo();
  } catch (_) {
    _setLoading(false);
  }
}

Clear all the tokens and storage after the successful logout process.

// lib/main.dart

void _clearSessionInfo() {
  _secureStorage.delete(key: 'refresh_token');
  setState(() {
    _isLoggedIn = false;
    _userName = null;
    _userInfo = null;
    _accessToken = null;
    _idToken = null;
    _refreshToken = null;
  });
}

Refreshing Tokens

Add the following section to renew tokens using refresh_token that we stored in local storage.

// lib/main.dart

Future<void> _refreshAuthToken() async {
  try {
    _setLoading(true);
    // Get new token using refresh token
    final TokenResponse? result = await _appAuth.token(TokenRequest(
        _clientId, _redirectUrl, refreshToken: _refreshToken, issuer: _issuer, scopes: _scopes));
    _processTokenResponse(result);
  } catch (_) {
    _clearSessionInfo(); // Clear all the tokens if an error occurs
  } finally {
    _setLoading(false);
  }
}

Extract new tokens from TokenResponse and store renewed Refresh Token in local storage.

//lib/main.dart

// Process refresh token exchange response
void _processTokenResponse(TokenResponse? response) {
  _secureStorage.write(key: 'refresh_token', value: response!.refreshToken);
  setState(() {
    _isLoggedIn = true;
    _accessToken = response.accessToken!;
    _idToken = response.idToken!;
    _refreshToken = response.refreshToken!;
  });
  _fetchUserInfo();
}

Add View

Finally, add the following section to your Widget to interact with the user.

//lib/main.dart

// ...

body: Center(
  child: Padding(
    padding: const EdgeInsets.all(12),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        Visibility(
          visible: _isLoading,
          child: const LinearProgressIndicator(), // Loading Bar
        ),
        Text('Welcome to PlusAuth Flutter Demo!',
            style: TextStyle( fontSize: 21.0 )
        ),
        Text('Username: ${_userName ?? '-'}',
            style: const TextStyle( fontSize: 18.0 )
        ),
        Visibility(
          child: ElevatedButton(
            child: const Text('Login'),
            onPressed: _signIn,
          ),
          visible: !_isLoggedIn,
        ),
        Visibility(
          child: ElevatedButton(
            child: const Text('Logout'),
            onPressed: _singOut,
          ),
          visible: _isLoggedIn,
        ),
        Text(_userInfo ?? '',
            style: const TextStyle( fontSize: 16.0 )
        ),
      ],
    ),
  )
),

// ...

See it in action

That's it. Start your app and point to your device or emulator. Follow the Log In link to log in or sign up to your PlusAuth tenant. After you click the Login button, the browser launches and shows the PlusAuth Login page. Upon successful login or signup, you should be redirected back to the application.