Flutter OAuth

Documentation

A drop-in OAuth implementation for Flutter apps

This is a generic OAuth 2.0 implementation that should work with compliant backends such as Azure B2C or Okta.

Features

  • Support for OAuth 2.0 auth code grant flow
  • Automatically handles showing a browser view for users to log-in
  • Catches access tokens when the browser redirects back to the app (assuming redirect schemes are set up correctly as detailed below)
  • Also supports persistence to avoid logging in every time the app starts

Getting started

Setup your OAuth backend

Your OAuth implementation should support auth code grant flow, with a mobile client set up. You should obtain the following urls / values:

  • Client ID
  • Issuer URL
  • Redirect URL
  • Post-logout redirect URL
  • Authorize URL
  • Token URL
  • Logout URL
  • Scopes (depending on end-user type)

Setup your app to handle custom scheme redirects

Follow the instructions in the flutter_appauth package so that the app can be notified of log-in redirects. The instructions are copied as follows:

Android setup

Go to the build.gradle file for your Android app to specify the custom scheme so that there should be a section in it that look similar to the following but replace <your_custom_scheme> with the desired value

...
android {
    ...
    defaultConfig {
        ...
        manifestPlaceholders += [
                'appAuthRedirectScheme': '<your_custom_scheme>'
        ]
    }
}

Please ensure that value of <your_custom_scheme> is all in lowercase as there've been reports from the community who had issues with redirects if there were any capital letters. You may also notice the += operation is applied on manifestPlaceholders instead of =. This is intentional and required as newer versions of the Flutter SDK has made some changes underneath the hood to deal with multidex. Using = instead of += can lead to errors like the following

Attribute application@name at AndroidManifest.xml:5:9-42 requires a placeholder substitution but no value for <applicationName> is provided.

If you see this error then update your build.gradle to use += instead.

If your app is target API 30 or above (i.e. Android 11 or newer), make sure to add the following 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 to specify the custom scheme so that there should be a section in it that look similar to the following but replace <your_custom_scheme> with the desired value

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string><your_custom_scheme></string>
        </array>
    </dict>
</array>

Warning: The AppAuth iOS SDK has some logic to validate the redirect URL to see if it should be responsible for processing the redirect. This appears to be failing under certain circumstances. Adding a trailing slash to the redirect URL specified in your code has been reported to fix the issue.


Usage

The AuthManager class is the main class for interacting with the current user state. It can be configured by setting up AuthClient and AuthPersistence classes.

const appAuth = FlutterAppAuth();

final client = AppAuthClient(
  appAuth: appAuth,
  clientId: 'CLIENT_ID',
  issuer: 'ISSUER_URL',
  redirectUrl: 'REDIRECT_URL',
  postLogoutRedirectUrl: 'POST_LOGOUT_REDIRECT_URL',
  authorizeUrl: 'AUTHORIZE_URL',
  tokenUrl: 'TOKEN_URL',
  logoutUrl: 'LOGOUT_URL',
  scopes: const [
    'openid', // standard scope for OpenID
    'offline_access', // standard scope for refresh tokens
    'SCOPE_1',
    'SCOPE_2',
  ],
);

final prefs = await StreamingSharedPreferences.instance;

final persistence = SharedPrefOAuthPersistence(prefs);

// Create the Auth Manager
final authManager = AuthManager(
  client: client,
  persistence: persistence,
);

// Listen to changes via the currentUser stream
authManager.currentUser.listen((user) => print(user));

// Log-in
await authManager.logIn();

// Read the current user
print(authManager.currentUserValue);

// Get auth header (for use in your Web API)
final headers = await authManager.getAuthHeader();
print(headers);

// Log-out
await authManager.logOut();

For a more detailed example, check the sample Flutter app in the example/ folder.


About OAuth 2.0

OAuth 2.0 supports various ways of logging in, such as auth code grant, client credentials grant, device code flow, on-behalf-of flow, implicit grant flow, or resource owner password credentials grant, among others. This package is designed to support the auth code grant flow, which is the recommended way for logging in for most consumer app types (single page apps, web apps, and natively installed apps).

In the auth code grant flow, the client app opens a web browser pointing to an authorize endpoint where the user can log in. If the user logs in successfully, the server redirects the browser to a known redirect URL with an authorization code in the query parameter. In mobile apps, the redirect URL typically has a custom scheme. Android and iOS can then open / notify your client app if it's registered to handle a specific custom scheme.

Auth codes are very short-lived tokens that can be used to get a real access token. The client app should send the authorization code to the token endpoint, where it can be exchanged for an access token and optionally a refresh token. This package can handle the web browser log-in, redirect, and token request for you.

Access tokens are sent in the header of every Web API request, in the form Authorization: bearer <ACCESS TOKEN>. They are the actual token used to verify the client's identity and access resources. Because they are transmitted often, they are more at risk of being stolen. This is why access tokens typically have a short lifespan of several minutes, to minimize the potential damage to the user if they ever are stolen.

When the access token expires, the client app can use the refresh token to request for a new acess token by calling the same token endpoint. Refresh tokens have a longer lifespan since they are used less often and are less at risk of being stolen.

Libraries

flutter_oauth
A drop-in OAuth implementation for Flutter apps