The Kinde’s Flutter SDK allows developers to integrate Kinde Authentication into their Flutter projects. Integrate Kinde authentication with your Flutter app. When you configure, register, log in, and log out, the authentication state is securely stored across app restarts.

Register with Kinde

Link to this section

If you haven’t already got a Kinde account, register for free here (no credit card required). Registering gives you a Kinde domain, which you need to get started, e.g. yourapp.kinde.com.

Before you install

Link to this section

This SDK is suitable for:

  • Flutter 3.10.0 or later.
  • Dart 3.0.6 or later.

KindeSDK is available through pub.dev. To install it, simply add the following line to your pubspec.yaml:

kinde_flutter_sdk: <latest-version>

Integrate with your app

Link to this section

The kinde_flutter_sdk package is intended to work with Flutter Projects.

Within the main function, ensure WidgetsFlutterBinding is initialized. KindeFlutterSDK.initializeSDK() must be called before using the SDK. If you’re using an .env file, provide the .env filename inside the load function. See below:

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:kinde_flutter_sdk/kinde_flutter_sdk.dart';

WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
await KindeFlutterSDK.initializeSDK(
    authDomain: dotenv.env['KINDE_AUTH_DOMAIN']!,
    authClientId: dotenv.env['KINDE_AUTH_CLIENT_ID']!,
    loginRedirectUri: dotenv.env['KINDE_LOGIN_REDIRECT_URI']!,
    logoutRedirectUri: dotenv.env['KINDE_LOGOUT_REDIRECT_URI']!,
    audience: dotenv.env['KINDE_AUDIENCE'], //optional
		scopes: ["email","profile","offline","openid"] // optional
);

Note: To setup the .env file in your flutter package, check the flutter_dotenv package.

Environment variables:

Link to this section

Put these variables in your .env file. You can find these variables on your Settings > Applications > [Your app] > View details page.

  • KINDE_AUTH_DOMAIN - your Kinde domain
  • KINDE_CLIENT_ID - your Kinde client ID
  • KINDE_LOGIN_REDIRECT_URI - your callback url to redirect to after authentication. Make sure this URL is under your Allowed callback URLs.
  • KINDE_LOGOUT_REDIRECT_URI - where you want users to be redirected to after logging out. Make sure this URL is under your Allowed logout redirect URLs.
  • KINDE_AUDIENCE (optional)- the intended recipient of an access token. To fetch this value, go to Settings > Applications > [Your app] > APIs

Below is an example of a .env file

KINDE_AUTH_DOMAIN=https://<your_kinde_subdomain>.kinde.com
KINDE_AUTH_CLIENT_ID=<your_kinde_client_id>
KINDE_LOGIN_REDIRECT_URI=<your_custom_scheme>://kinde_callback
KINDE_LOGOUT_REDIRECT_URI=<your_custom_scheme>://kinde_logoutcallback
KINDE_AUDIENCE=<your_kinde_audience>

Example:

KINDE_AUTH_DOMAIN='https://myapp.kinde.com'
KINDE_AUTH_CLIENT_ID='clientid'
KINDE_LOGIN_REDIRECT_URI='com.kinde.myapp://kinde_callback'
KINDE_LOGOUT_REDIRECT_URI='com.kinde.myapp://kinde_logoutcallback'
KINDE_AUDIENCE='myapp.kinde.com/api'

Note: Be sure to add the .env file in your .gitignore file. See the GitHub link here.

Configure your app

Link to this section

Android Setup

Go to the build.gradle file in the Android > App folder for your Android app.

Specify the custom scheme similar to the following, but replace <your_custom_scheme> with your own value.

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

iOS Setup

Go to the Info.plist located at ios > Runner for your iOS/macOS app.

Specify the custom scheme similar to the following but replace <your_custom_scheme> with your own value.

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

Note: <your_custom_scheme> has been defined previously as com.kinde.app You can define your own custom scheme to correspond to the package name.

Set callback URLs

Link to this section

For your app to work with Kinde, you need to set callback and logout redirect URLs.

Replace the values you see in <code brackets> with your own values.

  1. In Kinde, go to Settings > Applications.
  2. Select View details on your app.
  3. Scroll down to the Callback URLs section.
  4. Add in the callback URLs for your app, which might look something like this:
    • Allowed callback URLs: <your_custom_scheme>://kinde_callback
    • Allowed logout redirect URLs: <your_custom_scheme>://kinde_logoutcallback
  loginRedirectUri: 'com.kinde.myapp://kinde_callback',
  logoutRedirectUri: 'com.kinde.myapp://kinde_logoutcallback',
  1. Select Save.

Tip: Make sure there are no hidden spaces in URLs and remove the ‘/’ backslash at the end.

Note: For more details, please visit the link Set callback and redirect URLs.

If you would like to use different Environments as part of your development process, you will need to add them within your Kinde business first. You will also need to add the Environment subdomain to the code block above.

Sign in and sign up

Link to this section

Kinde supports an easy to implement login / register flow. Use the functions examples below to redirect your users to Kinde, where they authenticate before being redirected back to your app.

Make sure you’ve already defined KindeFlutterSDK.instance variable.

final sdk = KindeFlutterSDK.instance;

Redirect after authentication

Link to this section

The Kinde client provides methods for a simple login / register flow which authenticates the user and redirects them back to the app.

...
final String token = await sdk.login();
// or sdk.login(type: AuthFlowType.pkce) for apply pkce flow
...
await sdk.register();
// or sdk.register(type: AuthFlowType.pkce) for apply pkce flow
...

Note: Kinde supports the PKCE extension, in which the code_challenge and  code_challenge_method  parameters are also required. For More information, visit Using OAuth Scopes.

This is implemented in much the same way as logging in or registering. The Kinde SDK client comes with a logout method.

....
await sdk.logout()
....

Register your first user by signing up yourself. You’ll see your newly registered user on the Users page in Kinde.

Get user information

Link to this section

To access the user information, call one of the getUser or getUserProfileV2 methods.

sdk.getUser().then((value) {
     print('User: ${value?.firstName ?? '
         '} ${value?.lastName ?? '
         '}');
 });
final userProfile = await sdk.getUserProfileV2();
print(userProfile);

// returns
UserProfile {
  id=kp_12345678910,
  preferredEmail=dave.com,
  lastName=smith,
  firstName=dave,
}

Both method returns the sub(unique id of user In Kinde Console), id, firstName, lastName, picture, and preferredEmail, etc.

Check if user is authenticated

Link to this section

We’ve provided a helper to get a boolean value to check if a user is signed in by verifying that the access token is still valid.

final isAuthenticated = await sdk.isAuthenticate();
if (isAuthenticated) {
	// Need to implement, e.g: call an api, etc...
} else {
	// Need to implement, e.g: redirect user to sign in, etc..
}

User permissions

Link to this section

Once the user has logged in, the JWT token is returned which contains an array of permissions of that user. Read the permissions and implement the functionality in your application accordingly.

Set permissions in your Kinde account. Here’s an example set of permissions.

List<String> permissions = [
    "create:todos",
    "update:todos",
    "read:todos",
    "delete:todos",
    "create:tasks",
    "update:tasks",
    "read:tasks",
    "delete:tasks",
];

We provide helper functions to more easily access the permissions claim:

final ClaimPermissions sdkPermissions = sdk.getPermissions(); //to fetch list of permissions
final ClaimPermission sdkPermission = sdk.getPermission('permissionName'); //to fetch a single permission

To fetch a list of permissions, we use the following permission function:

Future<void> getPermissions() async {
    final ClaimPermissions sdkPermissions = sdk.getPermissions();
    print(sdkPermissions.permissions); //list of claim permissions granted to user
  }

To fetch a single permission, we use the following permission function:

Future<void> getPermission() async {
    final ClaimPermission sdkPermission = sdk.getPermission('permissionName');
    print(sdkPermission.isGranted); //Check if permission is granted.
		print(sdkPermission.orgCode); //orgCode for specific organization.
  }

Call Your API (Token storage)

Link to this section

After the user has successfully logged in, you will have a JSON Web Token (JWT) and a refresh token securely stored. You can retrieve an access token by utilizing the getToken method.

final access_token = await sdk.getToken();

If the accessToken has expired, but the refreshToken is valid, the getToken method will automatically fetch the newToken using the built in refreshToken interceptor. To implement the methods in your front-end application with encrypted storage using flutter_secure_storage and hive, use the following code:

import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:kinde_flutter_sdk/kinde_flutter_sdk.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';

Future<void> initEncryptedHive() async {
    const FlutterSecureStorage secureStorage =  FlutterSecureStorage();
  var containsEncryptionKey = await secureStorage.containsKey(key: 'encryptionKey');
  if (!containsEncryptionKey) {
    var key = Hive.generateSecureKey();
    await secureStorage.write(key: 'encryptionKey', value: base64UrlEncode(key));
  }

}
void main(){
 WidgetsFlutterBinding.ensureInitialized();
 await initEncryptedHive();
 await dotenv.load(fileName: ".env");
 await KindeFlutterSDK.initializeSDK(
    authDomain: dotenv.env['KINDE_AUTH_DOMAIN']!,
    authClientId: dotenv.env['KINDE_AUTH_CLIENT_ID']!,
    loginRedirectUri: dotenv.env['KINDE_LOGIN_REDIRECT_URI']!,
    logoutRedirectUri: dotenv.env['KINDE_LOGOUT_REDIRECT_URI']!,
    audience: dotenv.env['KINDE_AUDIENCE'],
 );
}
Future < Box > hiveEncryptedBox() async {
    // Hive Encrypted Box Added
    const FlutterSecureStorage secureStorage = FlutterSecureStorage();
    String ? key = await secureStorage.read(key: 'encryptionKey');
    var encryptionKey = base64Url.decode(key!);
    var box = await Hive.openBox('myBox', encryptionCipher: HiveAesCipher(encryptionKey));
    return box;
}
Future < String > returnAccessToken() async {
    final box = await hiveEncryptedBox();
    var token = box.get('token', defaultValue: '');
    if (token == '') {
        return await getNewToken();
    } else if (token != null) {
        bool hasExpired = JwtDecoder.isExpired(token);
        if (hasExpired) {
            return await getNewToken();
        }
        return token;
    } else {
        return getNewToken();
    }
}
Future < String > getNewToken() async {
    String ? token = await sdk.getToken();
    if (token == null) return 'Refresh Token Expired'; // Redirect user to the login page
    var box = await hiveEncryptedBox();
    await box.put('token', token);
    return token;
}

Note: To setup hive and flutter_secure_storage, visit the pub.dev .

You may also use Android KeyStore or iOS Realm to store tokens.

An audience is the intended recipient of an access token - for example the API for your application. The audience argument can be passed to the Kinde client to request an audience be added to the provided token.

The audience of a token is the intended recipient of the token.

await KindeFlutterSDK.initializeSDK(
   ...
   audience: 'myapp.kinde.com/api',
   ...
);

For details on how to connect, see Register an API.

Create an organization

Link to this section

To have a new organization created within your application, you will need to set up the following function:

Future<void> createOrg() async {
   await sdk.createOrg(orgName: string);
}

Optional type parameter

Link to this section
Future<void> createOrg() async {
   await sdk.createOrg(orgName: string,type: AuthFlowType.pkce);
}

Sign up and sign in to organizations

Link to this section

The Kinde client provides methods for you easily sign up and sign in users into organizations.

Future < void > loginUser() async {
    final token = await sdk.login(orgCode: 'orgCode');
    // or sdk.login(orgCode:'orgCode', type: AuthFlowType.pkce) for apply pkce flow
    print(token);
}
Future < void > registerUser() async {
    await sdk.register(orgCode: 'orgCode');
    // or sdk.register(orgCode:'orgCode', type: AuthFlowType.pkce) for apply pkce flow
}

Following authentication, Kinde provides a json web token (jwt) to your application. Along with the standard information we also include the org_code and the permissions for that organization (this is important as a user can belong to multiple organizations and have different permissions for each).

Example of a returned token:

{
    "aud": [], //audience
    "exp": 1658475930, //token expiry
    "iat": 1658472329,
    "iss": "https://your_subdomain.kinde.com",
    "jti": "123457890",
    "org_code": "org_1234", //organization codes
    "permissions": ["read:todos", "create:todos"], //list of permissions
    "scp": ["openid", "profile", "email", "offline"], //scopes
    "sub": "kp:123457890"
}

The id_token will also contain an array of organizations that a user belongs to - this is useful if you wanted to build out an organization switcher for example.

[
		...
		"org_codes": ["org_1234", "org_4567"]
		...
];

You can also fetch the org_code by:

  1. Sign into kinde.com.
  2. In Kinde, go to Organizations and fetch the org_code for which you want to register user’s.

Fetch organizations

Link to this section
sdk.getOrganization();
// ClaimOrganization(code: "org_1234")

sdk.getUserOrganizations();
// ClaimOrganizations(orgCodes: [Organization(code: "org_1234"), Organization(code: "org_abcd")])

To shift users between organizations visit user-management.

Default scopes

Link to this section

By default the KindeSDK requests the following scopes:

  • openid: Perform an OpenID connect sign-in.
  • profile: Retrieve the user’s profile.
  • offline: Retrieve a Refresh Token for offline access from the application.
  • email: Retrieve the user’s email.

Overriding scopes

Link to this section

You can override this by passing scope into the initializeSDK() function

await KindeFlutterSDK.initializeSDK(
    ...
    scopes = ["email", "profile"],
    ...
 );

Getting claims

Link to this section

We have provided a helper to grab any claim from your id or access tokens. The helper defaults to access tokens:

sdk.getClaim(claim:'aud'); // { name: "aud", value: ["local-testing@kinde.com"] }

sdk.getClaim(claim:'email',tokenType: TokenType.idToken);
// { name: "email", value: "first.last@test.com" }

sdk.getClaim(claim:'email',tokenType: TokenType.accessToken);

We have provided a helper to grab any feature flags from the access_token:

When a user signs in, the access token your product/application receives contains a custom claim called feature_flags, which is an object detailing the feature flags for that user. Here’s an example of how to fetch feature_flags directly from the jwt token.

void getFeatureFlags() async {
    String ? token = await sdk.getToken();
    if (token != null) {
        Map < String, dynamic > decoded = JwtDecoder.decode(token);
        print(decoded['feature_flags']);
    }
}

Decoding the token using the jwt.io website also references the following object that is contained within the token:

feature_flags: {
    theme: {
        "t": "s",
        "v": "pink"
    },
    is_dark_mode: {
        "t": "b",
        "v": true
    },
    competitions_limit: {
        "t": "i",
        "v": 5
    }
}

Note: Setup JwtDecoder using this link.

In order to minimize the payload in the token we have used single letter keys / values where possible. The single letters represent the following: t = type, v = value, s = String, b = Boolean, i = Integer,

You can set feature flags in your Kinde account by visiting: Add Feature Flags

We also provide helper functions to more easily access feature flags:

sdk.getFlag(code: "featureFlagCode",defaultValue: 'defaultValue',type: FlagType)

To fetch featureFlagCode, In Kinde, navigate to Releases > Feature Flags.

A practical example in code would look something like:

void getFlagInfo() {
    final Flag ? info = sdk.getFlag(code: 'featureFlagCode', defaultValue: '', type: FlagType.string);
    print(info?.code);
}

Wrapper functions

Link to this section

We also require wrapper functions by type which should leverage getFlag above.

Boolean wrapper

/**
 * Get a boolean flag from the feature_flags claim of the access_token.
 * @param {String} code - The name of the flag.
 * @param {Boolean} [defaultValue] - A fallback value if the flag isn't found.
 * @return {Boolean}
 */
sdk.getBooleanFlag(code, defaultValue);

sdk.getBooleanFlag("is_dark_mode");
// true

sdk.getBooleanFlag("is_dark_mode", false);
// true

sdk.getBooleanFlag("new_feature");
// Error - flag does not exist and no default provided

sdk.getBooleanFlag("new_feature", false);
// false (flag does not exist so falls back to default)

sdk.getBooleanFlag("theme", "blue");
// Error - Flag "theme" is of type string not boolean

String wrapper

/**
 * Get a string flag from the feature_flags claim of the access_token.
 * @param {String} code - The name of the flag.
 * @param {String} [defaultValue] - A fallback value if the flag isn't found.
 * @return {String}
 */
sdk.getStringFlag(code, defaultValue);

/* Example usage */
sdk.getStringFlag("theme");
// pink

sdk.getStringFlag("theme", "black");
// true

sdk.getStringFlag("cta_color");
// Error - flag does not exist and no default provided

sdk.getStringFlag("cta_color", "blue");
// blue (flag does not exist so falls back to default)

sdk.getStringFlag("is_dark_mode", false);
// Error - Flag "is_dark_mode" is of type boolean not string

Integer wrapper

/**
 * Get an integer flag from the feature_flags claim of the access_token.
 * @param {String} code - The name of the flag.
 * @param {Integer} [defaultValue] - A fallback value if the flag isn't found.
 * @return {Integer}
 */
sdk.getIntegerFlag(code, defaultValue);

sdk.getIntegerFlag("competitions_limit");
// 5

sdk.getIntegerFlag("competitions_limit", 3);
// 5

sdk.getIntegerFlag("team_count");
// Error - flag does not exist and no default provided

sdk.getIntegerFlag("team_count", 2);
// false (flag does not exist so falls back to default)

sdk.getIntegerFlag("is_dark_mode", false);
// Error - Flag "is_dark_mode" is of type boolean not integer

Example of wrapper function:

void getTheme() {
    final theme = sdk.getStringFlag("theme", "black");
    if (theme == "black") {
        //code to execute when theme is black.
    } else if (theme == "blue") {
        //code to execute when theme is blue.
    }
}
void isDarkMode() {
    final isDarkMode = sdk.getBooleanFlag("is_dark_mode", false);
    if (isDarkMode) {
        //code to execute in dark mode.
    } else {
        // code to execute in light mode.
    }
}
int getTeamCount() {
    final teamCount = sdk.getIntegerFlag("team_count", 2);
    print(teamCount);
    return teamCount;
}

SDK API Reference

Link to this section

Either your Kinde instance url or your custom domain. e.g. https://yourapp.kinde.com/

Type: string

Required: Yes


The id of your application - get this from the Kinde admin area.

Type: string

Required: Yes


loginRedirectUri

Link to this section

The url that the user will be returned to after authentication.

Type: string

Required: Yes


logoutRedirectUri

Link to this section

Where your user will be redirected upon logout.

Type: string

Required: Yes


The audience claim for the JWT.

Type: string

Required: No


The scopes to be requested from Kinde.

Type: string

Required: No

Default: openid profile email offline


KindeSDK Methods

Link to this section

Constructs redirect url and sends user to Kinde to sign in.

Usage:

sdk.login();

Constructs redirect url and sends user to Kinde to sign up.

Usage:

sdk.register();

Logs the user out of Kinde.

Usage:

sdk.logout();

isAuthenticated

Link to this section

Check if the user is authenticated.

Usage:

sdk.isAuthenticated();

Sample output:

true or false

Constructs redirect url and sends user to Kinde to sign up and create a new org for your business.

Arguments:

 options?: CreateOrgURLOptions {
   org_name?: "string";
   org_code?: "string";
   state?: "string";
}

Usage:

sdk.createOrg(
  org_name: "org_1234"
);

Extract the provided claim from the provided token type in the current session, the returned object includes the provided claim.

Arguments:

claim: "string", 
tokenKey?: TokenType "access_token" | "id_token"

Usage:

sdk.getClaim(claim:"given_name", tokenType: TokenType);

Returns the state of a given permission.

Arguments:

 key: "string"

Usage:

sdk.getPermission("permission");

Sample output:

{ orgCode : 'org_1234', isGranted : true}

getPermissions

Link to this section

Returns all permissions for the current user for the organization they are logged into.

Usage:

sdk.getPermissions();

Sample output:

{ orgCode : 'org_1234', permissions : ['create:todos', 'update:todos', 'read:todos'] }

getOrganization

Link to this section

Get details for the organization your user is logged into.

Usage:

sdk.getOrganization();

Sample output:

{ orgCode : 'org_1234' }

getUserOrganizations

Link to this section

Gets an array of all organizations the user has access to.

Usage:

sdk.getUserOrganizations();

Sample output:

{ orgCodes: ['org_7052552de68', 'org_5a5c29381327'] }

Extracts the user details from the Id token obtained post authentication.

Usage:

sdk.getUser();

Sample output:

{
"id":"kp_12345678910",
"preferredEmail":"dave@smith.com",
"lastName":"smith",
"firstName":"dave",
}

Returns the access token obtained post authentication.

Usage:

sdk.getToken();

Sample output:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
    .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c;

getUserProfile

Link to this section

Extracts makes use of the getToken method above to fetch user details

Usage:

sdk.getUserProfile();

Sample output:

{ given_name: 'Dave', id: 'abcdef', family_name : 'Smith', email : 'mailto:dave@smith.com' }

Get a flag from the feature_flags claim of the access_token

Arguments:

code : "string"
defaultValue? :  FlagType[keyof FlagType]
flagType? : [key of FlagType]

interface FlagType {
    s: string;
    b: boolean;
    i: number;
}
interface GetFlagType {
    type?: 'string' | 'boolean' | 'number';
    value: FlagType[keyof FlagType];
    is_default: boolean;
    code: "string";
}

Usage:

sdk.getFlag(code:"theme");

Sample output:

{ "code": "theme", "type": "string", "value": "pink", "is_default": false }

getBooleanFlag

Link to this section

Get a boolean flag from the feature_flags claim of the access_token

Arguments:

code : "string"
defaultValue? :  boolean

Usage:

sdk.getBooleanFlag(code:"is_dark_mode");

Sample output:

true or false

Get a string flag from the feature_flags claim of the access_token

Arguments:

code : "string"
defaultValue? :  "string"

Usage:

sdk.getStringFlag(code:"theme");

Sample output:

pink

getIntegerFlag

Link to this section

Get an integer flag from the feature_flags claim of the access_token

Arguments:

code : "string"
defaultValue? :  number

Usage:

sdk.getIntegerFlag(code:"team_count");

Sample output:

2

If you need help connecting to Kinde, contact us at support@kinde.com.


Talk to us

If you can’t find what you’re looking for in our help center — email our team

Contact support