A Flutter package for OpenID Connect (OIDC).

Table of contents


Supported platforms

Android badge iOS badge

Preconditions

Authentication with OIDC requires the app to be registered with an identity provider. SBB uses Microsoft Entra ID for enterprise applications. You can manage your app registration using the self-service API or the SBB API Platform. Detailed documentation is available on this site.

Redirect URL

The redirect URL must contain scheme, host, and path components in the format scheme://host/path and be written in lowercase.

Example: myappname://myhost/redirect

MSAL redirect URL

Applications should use the Microsoft Entra ID-specific redirect URL format whenever possible:

  • Android: msauth://<PACKAGE_NAME>/<BASE64_URL_ENCODED_SIGNATURE>
  • iOS: msauth.<BUNDLE_ID>://auth

See the Microsoft documentation for Android and iOS.

⚠️ This plugin does not enforce the format for backward compatibility.

Setup

Android

Minimum SDK version: 24

Open the build.gradle.kts file of your app and set the minimum SDK version to 24 or above:

...
android {
    ...
    defaultConfig {
        ...
        minSdk = 24
        ...
    }
}

Add BrowserTabActivity to the AndroidManifest.xml of your app as a child of the <application> element. It handles the browser callback after authentication. The scheme, host, and path values must match the redirect URL registered with Microsoft Entra ID:

<activity
    android:name="com.microsoft.identity.client.BrowserTabActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="<scheme>"
            android:host="<host>"
            android:path="/<path>" />
    </intent-filter>
</activity>

You also need to request the following permissions:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

iOS

Minimum iOS deployment target: 17.0

Open the Info.plist of your iOS app to specify the custom scheme. It should contain a section similar to the following, with <scheme> replaced by the desired value.

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

Also add LSApplicationQueriesSchemes to enable integration with the Microsoft Authenticator app if installed:

<key>LSApplicationQueriesSchemes</key>
<array>
  <string>msauthv2</string>
  <string>msauthv3</string>
</array>

Finally, add your desired keychain access group to the app's keychain access groups entitlement. See Create OIDC client for more details.

Usage

Add dependency

Add sbb_oidc as a dependency in your pubspec.yaml file.

sbb_oidc: ^5.0.0

Create OIDC client

Create an instance of the OIDC client.

final client = SBBOpenIDConnect.createClient(
  config: OidcClientConfig(
    tenantId: <tenant_id>,
    clientId: <client_id>,
    redirectUrl: <redirect_url>,
    keychainAccessGroup: <keychain_access_group>,
  ),
  enableLogging: <true/false>,
);

Here, replace <client_id> and <redirect_url> with the values registered with your identity provider.

<tenant_id> is the unique Microsoft tenant ID of your organisation. The SBB tenant IDs are defined in sbb_tenant.dart. We recommend using these constants. To implement multi-tenant login, you must use common as the tenant ID.

<keychain_access_group> is used to cache tokens in iOS. Apps that share the same group get silent SSO between them. Use the app's bundle identifier to keep tokens private. The value must also be declared in the app's keychain access groups entitlement. For more information, see Sharing access to keychain items among a collection of apps.

Login

To authorize and authenticate end users, call the login() method. This performs an interactive authorization request. Upon successfully completing the request, the method should return an OIDC token that contains an access token you can use to access protected APIs.

final token = await client.login(
  scopes: <your_scopes>,
);

Get tokens

Access tokens are short-lived and must be refreshed as soon as they expire. Therefore, your app should not cache the token. Instead, request a token every time it is needed by calling getToken().

final token = await client.getToken(
  scopes: <your_scopes>,
  forceRefresh: false,
);

The OIDC client checks whether the token has expired and refreshes it automatically. You can also force a refresh by setting the forceRefresh argument to true.

Get data about the end-user

To get data about the signed-in end user, you can either use the ID token or call getUserInfo().

final userInfo = await client.getUserInfo(
  scopes: <your_scopes>,
);

Using getUserInfo() is not recommended because it requires multiple HTTP requests to retrieve the data. The ID token contains the same data and requires at most one request if the token must be refreshed.

Get the SBB uid

The SBB UID (u/e number) is specified in the ID token as the sbbuid claim.

final oidcToken = ....
final idToken = JsonWebToken.decode(oidcToken.idToken);
final uid = idToken.payload['sbbuid'] as String;

Logout

Logging out deletes all OIDC tokens from the local cache. The user's session remains active on the server, so the user can sign in again without providing credentials.

await client.logout();

End session

Ending the session logs the user out of the built-in browser and deletes all cached OIDC tokens. The user must provide their credentials to log in again after ending the session.

await client.endSession();

Access multiple APIs

Azure AD has a security limitation: an access token can only be used for one API. An access token can have multiple scopes for one API, but it cannot contain scopes for other APIs. To use multiple APIs, you must request additional tokens with the scopes for the corresponding APIs. This means that the OIDC client has one access token for each API.

Suppose your app needs access to three different APIs:

  1. Microsoft Graph with read access to User and Calendar
  2. Api 1
  3. Api 2

The first step is to log in. As mentioned above, you can use the scopes of only one API, in this case Microsoft Graph. The scopes for this API are:

openid, profile, email, offline_access, Calendars.Read, User.Read,
final token = await client.login(
  scopes: [
    'openid',
    'profile',
    'email',
    'offline_access',
    'Calendars.Read',
    'User.Read',
  ],
);

The returned token can only be used to access the Microsoft Graph API. To access the other APIs (API 1 and API 2), you must request one additional token for each API using the getToken() method.

The scopes for API 1 are:

openid, offline_access, api://aaaaaaaa-1111-2222-3333-444444444444/.default,
final tokenForApi1 = await client.getToken(
  scopes: [
    'openid',
    'offline_access',
    'api://aaaaaaaa-1111-2222-3333-444444444444/.default',
  ],
);

The scopes for API 2 are:

openid, offline_access, api://bbbbbbbb-1111-2222-3333-444444444444/.default,
final tokenForApi2 = await client.getToken(
  scopes: [
    'openid',
    'offline_access',
    'api://bbbbbbbb-1111-2222-3333-444444444444/.default',
  ],
);

Multi-Factor authentication

Some APIs require multi-factor authentication (MFA), while others do not. In the example above, the Microsoft Graph API does not require MFA, but API 1 and API 2 do. Therefore, getToken() will throw a MultiFactorAuthenticationException. In this case, you must call login() a second time and use the scopes of an API that requires MFA.

final tokenForApi1 = await client.login(
  scopes: [
    'openid',
    'offline_access',
    'api://aaaaaaaa-1111-2222-3333-444444444444/.default',
  ],
);

This opens a pop-up where the user can enter the second factor.

Example

See example app.

Libraries

sbb_oidc