Using Kinde without an SDK
SDKs and APIs
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.
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.
This SDK is suitable for:
KindeSDK is available through pub.dev. To install it, simply add the following line to your pubspec.yaml:
kinde_flutter_sdk: <latest-version>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.
Put these variables in your .env file. You can find these variables on your Settings > Applications > [Your app] > View details page.
Below is an example of a .env file
KINDE_AUTH_DOMAIN=https://<your_kinde_subdomain>.kinde.comKINDE_AUTH_CLIENT_ID=<your_kinde_client_id>KINDE_LOGIN_REDIRECT_URI=<your_custom_scheme>://kinde_callbackKINDE_LOGOUT_REDIRECT_URI=<your_custom_scheme>://kinde_logoutcallbackKINDE_AUDIENCE=<your_kinde_audience>Example:
KINDE_AUTH_DOMAIN=https://myapp.kinde.comKINDE_AUTH_CLIENT_ID=clientidKINDE_LOGIN_REDIRECT_URI=myapp://kinde_callbackKINDE_LOGOUT_REDIRECT_URI=myapp://kinde_logoutcallbackKINDE_AUDIENCE=myapp.kinde.com/apiNote: Be sure to add the .env file in your .gitignore file. See the GitHub link here.
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 myapp You can define your own custom scheme to correspond to the app name.
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.
<your_custom_scheme>://kinde_callback<your_custom_scheme>://kinde_logoutcallback loginRedirectUri: myapp://kinde_callback, logoutRedirectUri: myapp://kinde_logoutcallback,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.
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;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(context: context);// or sdk.login(type: AuthFlowType.pkce, context: context) for apply pkce flow...await sdk.register(context: context);// or sdk.register(type: AuthFlowType.pkce, context: context) 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.
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);
// returnsUserProfile { id=kp_12345678910, preferredEmail=dave@smith.com, lastName=smith, firstName=dave,}Both method returns the sub(unique id of user In Kinde Console), id, firstName, lastName, picture, and preferredEmail, etc.
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..}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 permissionsfinal ClaimPermission sdkPermission = sdk.getPermission('permissionName'); //to fetch a single permissionTo 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. }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.
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);}Future<void> createOrg() async { await sdk.createOrg(orgName: string,type: AuthFlowType.pkce);}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:
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.
By default the KindeSDK requests the following scopes:
You can override this by passing scope into the initializeSDK() function
await KindeFlutterSDK.initializeSDK( ... scopes = ["email", "profile"], ... );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);}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 booleanString 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 stringInteger 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 integerExample 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;}authDomainEither your Kinde instance url or your custom domain. e.g. https://yourapp.kinde.com/
Type: string
Required: Yes
clientIdThe id of your application - get this from the Kinde admin area.
Type: string
Required: Yes
loginRedirectUriThe url that the user will be returned to after authentication.
Type: string
Required: Yes
logoutRedirectUriWhere your user will be redirected upon logout.
Type: string
Required: Yes
audienceThe audience claim for the JWT.
Type: string
Required: No
scopeThe scopes to be requested from Kinde.
Type: string
Required: No
Default: openid profile email offline
loginConstructs redirect url and sends user to Kinde to sign in.
Usage:
sdk.login();registerConstructs redirect url and sends user to Kinde to sign up.
Usage:
sdk.register();logoutLogs the user out of Kinde.
Usage:
sdk.logout();isAuthenticatedCheck if the user is authenticated.
Usage:
sdk.isAuthenticated();Sample output:
true or falsecreateOrgConstructs 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");getClaimExtract 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_tokenUsage:
sdk.getClaim(claim:"given_name", tokenType: TokenType);getPermissionReturns the state of a given permission.
Arguments:
key: "stringUsage:
sdk.getPermission("permission");Sample output:
{ orgCode : 'org_1234', isGranted : true}getPermissionsReturns 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'] }getOrganizationGet details for the organization your user is logged into.
Usage:
sdk.getOrganization();Sample output:
{ orgCode : 'org_1234' }getUserOrganizationsGets an array of all organizations the user has access to.
Usage:
sdk.getUserOrganizations();Sample output:
{ orgCodes: ['org_7052552de68', 'org_5a5c29381327'] }getUserExtracts 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",}getTokenReturns the access token obtained post authentication.
Usage:
sdk.getToken();Sample output:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ .SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c;getUserProfileExtracts 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' }getFlagGet 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 }getBooleanFlagGet a boolean flag from the feature_flags claim of the access_token
Arguments:
code : "string"defaultValue? : booleanUsage:
sdk.getBooleanFlag(code:"is_dark_mode");Sample output:
true or falsegetStringFlagGet a string flag from the feature_flags claim of the access_token
Arguments:
code : "string"defaultValue? : "stringUsage:
sdk.getStringFlag(code:"theme");Sample output:
pinkgetIntegerFlagGet an integer flag from the feature_flags claim of the access_token
Arguments:
code : "string"defaultValue? : numberUsage:
sdk.getIntegerFlag(code:"team_count");Sample output:
2If you need help connecting to Kinde, contact us at support@kinde.com.