Using Kinde without an SDK
SDKs and APIs
The Kinde Java SDK allows developers to connect their Java app to Kinde.
You can also find our Java docs and Java starter kit in GitHub.
The SDK is officially only supported for version 8 or later of Java.
If you haven’t already created a Kinde account, register for free here (no credit card required). Registration gives you a Kinde domain, which you will need to get started. e.g. yourapp.kinde.com.
Create a JAR file of your SDK project using the below command:
mvn clean installTo deploy it to a remote Maven repository instead, configure the settings of the repository and execute:
mvn clean deployRefer to the OSSRH Guide for more information.
Kinde’s SDK is available through Maven. To install it, simply add the following line to your pom.xml
<dependency> <groupId>com.kinde</groupId> <artifactId>java-sdk</artifactId> <version>1.0.0</version></dependency>Add this dependency to your project’s build file:
repositories { mavenCentral() // Needed if the ‘kinde’ jar has been published to maven central. mavenLocal() // Needed if the ‘kinda’ jar has been published to the local maven repo. }
dependencies { implementation "com.kinde:java-sdk:1.0.0” }Generate the JAR by executing the following code:
mvn clean packageThen manually install the following JARs:
Create KindeClientSDK object to use the SDK methods.
@PostConstructpublic void updateKindeClientSDK(){ this.kindeClientSDK=new KindeClientSDK( domain, redirectUri, clientId, clientSecret, grantType, logoutRedirectUri );}Add @ComponentScan annotation in your main application. It should include the packages where your controllers from both the main application and the dependency project are located.
@ComponentScan(basePackages = {"com.example.demo.controller", "org.openapitools.api", "org.openapitools.model", "org.openapitools.configuration"})For your app to work with Kinde, you need to set callback and logout redirect URLs.
http://localhost:8080/api/auth/kinde_callbackhttp://localhost:8080Tip: Make sure there are no hidden spaces in URLs and remove the ‘/’ backslash at the end.
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.
Add following properties in the application.properties file:
The following variables need to be replaced in the code snippets below.
https://your_kinde_domain.kinde.comhttp://localhost:8080/api/auth/kinde_callbackhttp://localhost:8080KindeClientSDK struct implements three OAuth flows: client_credentials, authorization_code, authorization_code_flow_pkceKinde supports an easy to implement login / register flow.
To redirect users to the /login and /register pages on the client side when a link is clicked, you can use HTML anchor tags. Here’s an example:
<a href="/login">Sign in</a>
<a href="/register">Sign up</a>After the user is redirected to the /login or /register page, your server will need to handle the authentication process. The SDK provides login and register methods that you can use to handle these requests on the server side. Use these methods in your server-side code:
Object resp=kindeClientSDK.login(response);Object resp=kindeClientSDK.register(response);When the user is redirected back to your site from Kinde, this will call your callback URL defined in the kinde.redirect.url variable. You will need to route /api/auth/kinde_callback to call a function to handle this.
@GetMapping("/api/auth/kinde_callback")public RedirectView callback(@RequestParam("code") String code, @RequestParam("state") String state, HttpServletResponse response, HttpServletRequest request) throws Exception { RedirectView redirectView=new CallbackController(this.kindeClientSDK).callback(code,state,response,request); return redirectView;}The Kinde SPA client comes with a logout method.
RedirectView redirectView=this.kindeClientSDK.logout(response);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, use the getUserDetails helper function:
Object userDetails = this.kindeClientSDK.getUserDetails(request);// returns[ 'given_name' => 'Dave', 'id' => 'abcdef', 'family_name' => 'Smith', 'email' => 'dave@smith.com', 'picture' => 'https://link_to_avatar_url.kinde.com',]After a user signs in and they are verified, the token return includes permissions for that user. User permissions are set in Kinde, but you must also configure your application to unlock these functions.
Example of a permissions array.
String[] permissions = { "create:todos", "update:todos", "read:todos", "delete:todos", "create:tasks", "update:tasks", "read:tasks", "delete:tasks",};Helper functions for permission access.
Object permission=this.kindeClientSDK.getPermission(request,"create:todos");// ["orgCode" => "org_1234", "isGranted" => true]Example in your code.
Object permissions=this.kindeClientSDK.getPermissions(request);// ["orgCode" => "org_1234", "permissions" => ["create:todos", "update:todos", "read:todos"]]Once the user has successfully authenticated, you’ll have a JWT and a refresh token and that has been stored securely. Use the getToken method to get the token:
Object token=this.kindeClientSDK.getToken(resp,request);Use the getAccessToken method of the Storage class to get an access token.
...import org.openapitools.sdk.storage.Storage;...
private Storage storage;
this.storage = Storage.getInstance();
String accessToken=storage.getAccessToken(req);
System.out.println(accessToken);The token will be stored in the cookie. To specify the expiration time, use the setTokenTimeToLive method.
setTokenTimeToLive(System.currentTimeMillis() + 3600000) // Live in 1 hourGet the current authentication status with isAuthenticated.
Boolean isAuthenticated = this.kindeClientSDK.isAuthenticated(request, resp);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.
@PostConstructpublic void createKindeClientSDK(){ this.kindeClientSDK=new KindeClientSDK( domain, redirectUri, clientId, clientSecret, grantType, logoutRedirectUri, Collections.singletonMap("audience","api.yourapp.com"));}To create a new organization within your application, run a similar function to below:
this.kindeClientSDK.createOrg(resp);You can also pass org_name as your organization
Map<String,Object> additonalParameters = new HashMap<>();additonalParameters.put("org_name","Your Organization");Object result = kindeClientSDK.createOrg(response,additonalParameters);When a user signs up or in to an organization, the org_code needs to be passed with the request. The org_code refers to the one created automatically in Kinde when the organization was created, for example org_0e9f496742ae.
Here’s an helper function for registering in below:
Map<String,Object> additonalParameters = new HashMap<>();additonalParameters.put("org_code","your_org_code");Object result = kindeClientSDK.register(response,additonalParameters);If you want a user to sign in to a particular organization, pass this code along with the sign in method.
Map<String,Object> additonalParameters=new HashMap<>();additonalParameters.put("org_code","your_org_code");Object result = kindeClientSDK.login(response,additonalParameters);Because a user can belong to multiple organizations, and because they may have different permissions for the different organizations, with pass both the org_code and permissions in the token. Here’s an example:
[ "aud" => [], "exp" => 1658475930, "iat" => 1658472329, "iss" => "https://your_subdomain.kinde.com", "jti" => "123457890", "org_code" => "org_1234", "permissions" => ["read:todos", "create:todos"], "scp" => [ "openid", "profile", "email", "offline ], "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"], ...];There are two helper functions you can use to extract information:
this.kindeClientSDK.getOrganization(request);// ["orgCode" => "org_1234"]
this.kindeClientSDK.getUserOrganizations(request);// ["orgCodes" => ["org_1234", "org_abcd"]]By default, Kinde requests the following scopes:
You can also pass override scopes to the Kinde SDK as per this example:
@PostConstructpublic void createKindeClientSDK(){ this.kindeClientSDK=new KindeClientSDK( domain, redirectUri, clientId, clientSecret, grantType, logoutRedirectUri, "profile email offline openid");}Kinde provides a helper to grab any claim from your ID or access tokens. The helper defaults to access tokens:
Object claim=this.kindeClientSDK.getClaim(request,"aud");// ["name" => "aud", "value" => ["api.yourapp.com"]]
Object claim=this.kindeClientSDK.getClaim(request,"given_name","id_token");// ["name" => "aud", "value" => "David"]When a user signs in, the access token your product or application receives contains a custom claim called feature_flags. Feature flags define what features a user can access after they sign in.
You can set feature flags in Kinde through code (see below), or through the Kinde application.
<u>**Create feature flag**</u>
_**POST**_ /api/v1/feature_flags
**Content-Type:** application/json**Accept:** application/json**Authorization:** Bearer {access-token}
<u>**Example:**</u>_**POST**_ https://{businessName}.kinde.com/api/v1/feature_flags
<u>**Request Body**</u>{ "name": "string", "description": "string", "key": "string", "type": "str", "allow_override_level": "env", "default_value": "string"}To minimize the token payload we use single letter keys / values to represent the following:
t = typev = values = stringb = booleani = integerWe also provide helper functions to more easily access feature flags:
this.kindeClientSDK.getFlag(request,"theme");A practical example in code would look something like:
this.kindeClientSDK.getFlag(request,"theme");// returns[ "code" => "theme", "type" => "string", "value" => "pink", "is_default" => false // whether the fallback value had to be used]
// Another usage casethis.kindeClientSDK.getFlag(request,"is_dark_mode");// returns[ "code": "is_dark_mode", "type": "boolean", "value": true, "is_default": false]
// This flag does not exist - default value providedMap<String,Object> options=new HashMap<>();options.put("defaultValue",false);this.kindeClientSDK.getFlag(request,"create_competition",options);// returns[ "code": "create_competition", "type" => "boolean", "value": false, "is_default": true // because fallback value had to be used]
// The flag type was provided as string, but it is an integerMap<String,Object> options=new HashMap<>();options.put("defaultValue",3);this.kindeClientSDK.getFlag(request,"competitions_limit",options,"s");
// should error out - Flag "competitions_limit" is type integer - requested type string
// This flag does not exist, and no default value providedthis.kindeClientSDK.getFlag(request,"new_feature");// should error out - This flag was not found, and no default value has been providedWe also require wrapper functions to be separated by type as part of the getFlag function.
Boolean wrapper:
this.kindeClientSDK.getBooleanFlag(request,"is_dark_mode");String wrapper:
this.kindeClientSDK.getStringFlag(request,"theme");Integer wrapper:
this.kindeClientSDK.getIntegerFlag(request,"competitions_limit");Example of wrapper function:
// [--- Boolean ---]this.kindeClientSDK.getBooleanFlag(request,"is_dark_mode");
// with default valuethis.kindeClientSDK.getBooleanFlag(request,"is_dark_mode", false);// [--- Boolean ---]
// [--- String ---]this.kindeClientSDK.getStringFlag(request,"theme");
// with default valuethis.kindeClientSDK.getStringFlag(request,"theme","blue");// [--- String ---]
// [--- Integer ---]this.kindeClientSDK.getIntegerFlag(request,"competitions_limit");
// with default valuethis.kindeClientSDK.getIntegerFlag(request,"competitions_limit", 1);// [--- Integer ---]This applies to frontend SDKs only.
By default the JWTs provided by Kinde are stored in memory. This protects you from both CSRF attacks (possible if stored as a client side cookie) and XSS attacks (possible if persisted in local storage).
The trade off with this approach is that if a page is refreshed or a new tab is opened, then the token is wiped from memory, and the sign in button would need to be clicked to re-authenticate.
There are two ways to prevent this behavior:
isDangerouslyUseLocalStorage. This SHOULD NOT be used in production. This will store only the refresh token in local storage to silently re-authenticate.Storing Tokens
The setToken method is used to store the token in a secure manner. It encodes the token (which can be a string or a map) and sets it as a cookie in the HTTP response. The token is stored with specific attributes such as MaxAge, Path, Domain, Secure, and HttpOnly to enhance security.
public static void setToken(HttpServletResponse response, Object token) { // Encode the token (either as a string or a map) String tok = (token instanceof String) ? (String) token : encodeTokenAsURL(token);
// Set the token as a cookie with defined attributes setItem(response, StorageEnums.TOKEN.getValue(), tok, getTokenTimeToLive().intValue());}The setItem method is a utility function for setting a cookie with the provided attributes.
public static void setItem(HttpServletResponse response, String key, String value, int expiresOrOptions, String path, String domain, boolean secure, boolean httpOnly) { String newKey = getKey(key); Cookie cookie = new Cookie(newKey, value); cookie.setMaxAge(expiresOrOptions); cookie.setPath(path); cookie.setDomain(domain); cookie.setSecure(secure); cookie.setHttpOnly(httpOnly); response.addCookie(cookie);}Retrieving Tokens
Object token=this.kindeClientSDK.getToken(response,request);The getToken method is used to retrieve the token from the HTTP request. It decodes and reads the stored token from the cookie.
public static Map<String, Object> getToken(HttpServletRequest request) { try { // Get the token as a string String token = getItem(request, StorageEnums.TOKEN.getValue());
// Decode the token and convert it to a Map String decodedToken = java.net.URLDecoder.decode(token, "UTF-8"); return new ObjectMapper().readValue(decodedToken, new TypeReference<Map<String, Object>>() {}); } catch (Exception e) { return null; }}The getItem method is a utility function for retrieving a specific cookie from the HTTP request.
public static String getItem(HttpServletRequest request, String key) { String cookieName = getKey(key); Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals(cookieName)) { return cookie.getValue(); } } } return "";}Either your Kinde instance URL or your custom domain. e.g. https://yourapp.kinde.com/
Type: string
Required: yes
redirectUriThe URL that the user will be returned to after authentication.
Type: string
Required: yes
clientIdThe unique ID of your application. Get this from the Application details section in Kinde.
Type: string
Required: yes
clientSecretThe unique client secret of your Kinde application. Get this from the Application details section in Kinde.
Type: string
Required: Not required if you use OAuth PKCE
grantTypeThe grantType for Kinde Authorization varies for each OAuth 2 flow. You can use:
code_challenge and code_challenge_method parameters are also required for this grant type.Type: string
Required: yes
logoutRedirectUriWhere your user will be redirected when they sign out.
Type: string
Required: yes
scopeThe scopes to be requested from Kinde.
Type: string
Required: No
Default: openid profile email offline
additionalParametersAdditional parameters that will be passed in the authorization request.
Type: Map<String, Object>
Required: No
Default: new Hashmap<>()
additionalParameters - audienceThe audience claim for the JWT.
Type: string
Required: No
loginConstructs redirect URL and sends user to Kinde to sign in.
Arguments:
response?: HttpServletResponseadditionalParameters?: Map<String, Object> //org_code -> StringUsage:
kindeClientSDK.login(response);registerConstructs redirect url and sends user to Kinde to sign up.
Arguments:
response?: HttpServletResponseadditionalParameters?: Map<String, Object> //org_code -> StringUsage:
kindeClientSDK.register(response);logoutLogs the user out of Kinde.
Arguments:
response?: HttpServletResponseUsage:
this.kindeClientSDK.logout(response);getTokenReturns the raw access token from URL after logged from Kinde.
Arguments:
response?: HttpServletResponserequest?: HttpServletRequestUsage:
kindeClientSDK.getToken(response,request);Sample output:
[ "access_token" => "eyJhbGciOiJSUzI...",
"expires_in" => 86400,
"id_token" => "eyJhbGciOiJSU...",
"refresh_token" => "yXI1bFQKbXKLD7AIU...",
"scope" => "openid profile email offline",
"token_type" => "bearer
];createOrgConstructs redirect url and sends user to Kinde to sign up and create a new org for your business.
Arguments:
response?: HttpServletResponseadditionalParameters?: Map<String, Object> //org_name -> StringUsage:
kindeClientSDK.createOrg(response);
or
Map<String,Object> additionalParameters=new HashMap<>();additionalParameters.put("org_name","your organization name");kindeClientSDK.createOrg(response,additionalParameters);Sample output:
RedirectView //Will return RedirectView if Grant type is either authorization_code or authorization_code_flow_pkce
LinkedHashMap //Will return LinkedHashMap if Grant type is client_credentialsgetClaimGets a claim from an access or ID token.
Arguments:
request?: HttpServletRequest,claim: string, tokenKey?: stringUsage:
kindeClientSDK.getClaim(request,"given_name", "id_token");Sample output:
DavidgetPermissionReturns the state of a given permission.
Arguments:
request?: HttpServletRequest,key: stringUsage:
this.kindeClientSDK.getPermission(request,"read:todos");Sample output:
[ "orgCode" => "org_1234", "isGranted" => true];getPermissionsReturns all permissions for the current user for the organization they are logged into.
Arguments:
request?: HttpServletRequestUsage:
this.kindeClientSDK.getPermissions(request);Sample output:
[ "orgCode" => "org_1234", "permissions" => ["create:todos", "update:todos", "read:todos"]];getOrganizationGet details for the organization your user is logged into.
Arguments:
request?: HttpServletRequestUsage:
this.kindeClientSDK.getOrganization(request);Sample output:
[ "orgCode" => "org_1234];getUserDetailsReturns the profile for the current user.
Arguments:
request?: HttpServletRequestUsage:
this.kindeClientSDK.getUserDetails(request);Sample output:
[ "given_name" => "Dave", "id" => "abcdef", "family_name" => "Smith", "email" => "mailto:dave@smith.com];getUserOrganizationsGets an array of all organizations the user has access to.
Arguments:
request?: HttpServletRequestUsage:
this.kindeClientSDK.getUserOrganizations(request);Sample output:
[ "orgCodes" => ["org_8de8711f46a", "org_820c0f318de"]];getFlagGets a feature flag from an access token. Arguments:
request?: HttpServletRequest,flagName: string, options?: Map<String, Object> //"defaultValue" => anyUsage:
this.kindeClientSDK.getFlag(request,"is_dark_mode");Sample output:
[ "code": "is_dark_mode", "type": "boolean", "value": true, "is_default": false];getBooleanFlagGets a boolean feature flag from an access token. Arguments:
request?: HttpServletRequest,flagName: string, defaultValue?: booleanUsage:
kindeClientSDK.getBooleanFlag(request,"is_dark_mode", false);Sample output:
[ "code": "is_dark_mode", "type": "boolean", "value": false, "is_default": true];getStringFlagGets a string feature flag from an access token. Arguments:
request?: HttpServletRequest,flagName: string, defaultValue?: stringUsage:
kindeClientSDK.getStringFlag(request,"theme");Sample output:
[ "code": "theme", "type": "string", "value": "black", "is_default": false];getIntegerFlagGets a integer feature flag from an access token. Arguments:
request?: HttpServletRequest,flagName: string, defaultValue?: integerUsage:
kindeClientSDK.getIntegerFlag(request,"competitions_limit");Sample output:
[ "code": "competitions_limit", "type": "integer", "value": 1, "is_default": false];isAuthenticatedTo check user authenticated or not. Arguments:
response?: HttpServletResponserequest?: HttpServletRequestUsage:
this.kindeClientSDK.isAuthenticated(request, response);Sample output:
true or falseIf you need help connecting to Kinde, please contact us at support@kinde.com.