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.

Supported versions

Link to this section

The SDK is officially only supported for version 8 or later of Java.

Register with Kinde

Link to this section

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 install

To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:

mvn clean deploy

Refer 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 package

Then manually install the following JARs:

  • target/java-sdk-1.0.0.jar
  • target/lib/*.jar

Integrate with your app

Link to this section

Create KindeClientSDK object to use the SDK methods.

@PostConstruct
public 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"})

Set callback URLs

Link to this section

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

  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 (also known as Redirect URIs)- http://localhost:8080/api/auth/kinde_callback
    • Allowed logout redirect URLs - http://localhost:8080
  5. Select Save.

Tip: 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.

Configure your app

Link to this section

Add following properties in the application.properties file:

The following variables need to be replaced in the code snippets below.

  • kinde.host - your Kinde domain. E.g. https://your_kinde_domain.kinde.com
  • kinde.redirect.url - your callback URL, make sure this URL is under your allowed callback redirect URLs. E.g. http://localhost:8080/api/auth/kinde_callback
  • kinde_post.logout.redirect.url - where you want users to be redirected to after signing out, make sure this URL is under your allowed logout redirect URLs. E.g. http://localhost:8080
  • kinde.client.id - you can find this on the Application details page
  • kinde.client.secret - you can find this on the Application details page
  • kinde.grant.type - The KindeClientSDK struct implements three OAuth flows: client_credentials, authorization_code, authorization_code_flow_pkce

Sign in and sign up

Link to this section

Kinde supports an easy to implement login / register flow.

Client-side redirection

Link to this section

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>

Server-side authentication handling

Link to this section

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);

Redirect after authentication

Link to this section

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.

Get user information

Link to this section

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',
]

User permissions

Link to this section

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 hour

Get 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.

@PostConstruct
public void createKindeClientSDK(){
   this.kindeClientSDK=new KindeClientSDK(
           domain,
           redirectUri,
           clientId,
           clientSecret,
           grantType,
           logoutRedirectUri,
           Collections.singletonMap("audience","api.yourapp.com"));
}

Create an organization

Link to this section

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);

Sign users up or in to an organization

Link to this section

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 into 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"]]

Default scopes

Link to this section

By default, Kinde requests the following scopes:

  • profile
  • email
  • offline
  • openid

Overriding scopes

Link to this section

You can also pass override scopes to the Kinde SDK as per this example:

@PostConstruct
public void createKindeClientSDK(){
   this.kindeClientSDK=new KindeClientSDK(
           domain,
           redirectUri,
           clientId,
           clientSecret,
           grantType,
           logoutRedirectUri,
           "profile email offline openid");
}

Getting claims

Link to this section

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 = type
  • v = value
  • s = string
  • b = boolean
  • i = integer

We 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 case
this.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 provided
Map<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 integer
Map<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 provided
this.kindeClientSDK.getFlag(request,"new_feature");
// should error out - This flag was not found, and no default value has been provided

Wrapper functions

Link to this section

We 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 value
this.kindeClientSDK.getBooleanFlag(request,"is_dark_mode", false);
// [--- Boolean ---]


// [--- String ---]
this.kindeClientSDK.getStringFlag(request,"theme");

// with default value
this.kindeClientSDK.getStringFlag(request,"theme","blue");
// [--- String ---]


// [--- Integer ---]
this.kindeClientSDK.getIntegerFlag(request,"competitions_limit");

// with default value
this.kindeClientSDK.getIntegerFlag(request,"competitions_limit", 1);
// [--- Integer ---]

Token storage in the authentication state

Link to this section

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:

  1. (Recommended) Use the Kinde custom domain feature. We can then set a secure, httpOnly cookie against your domain containing only the refresh token which is not vulnerable to CSRF attacks.
  2. (Non-production solution only) Use an escape hatch for local development: 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 "";
}

API reference - Types

Link to this section

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

Type: string

Required: yes

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

Type: string

Required: yes

The unique ID of your application. Get this from the Application details section in Kinde.

Type: string

Required: yes

The 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

The grantType for Kinde Authorization varies for each OAuth 2 flow. You can use:

  • Authorization code: GrantType.AUTHORIZATION_CODE.getValue(): Intended for confidential clients, e.g. web-servers.
  • Authorization code with PKCE: GrantType.PKCE.getValue(): Extension for public clients, e.g. single page web applications and mobile applications, and confidential clients, e.g. web-servers. Note that the code_challenge and code_challenge_method parameters are also required for this grant type.
  • Client credentials flow: GrantType.CLIENT_CREDENTIALS.getValue(): Intended for confidential clients where machine-to-machine communication is required.

Type: string

Required: yes

logoutRedirectUri

Link to this section

Where your user will be redirected when they sign out.

Type: string

Required: yes

The scopes to be requested from Kinde.

Type: string

Required: No

Default: openid profile email offline

additionalParameters

Link to this section

Additional parameters that will be passed in the authorization request.

Type: Map<String, Object>

Required: No

Default: new Hashmap<>()

additionalParameters - audience

Link to this section

The audience claim for the JWT.

Type: string

Required: No

API reference - Kinde methods

Link to this section

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

Arguments:

response?: HttpServletResponse
additionalParameters?: Map<String, Object> //org_code -> String

Usage:

kindeClientSDK.login(response);

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

Arguments:

response?: HttpServletResponse
additionalParameters?: Map<String, Object> //org_code -> String

Usage:

kindeClientSDK.register(response);

Logs the user out of Kinde.

Arguments:

response?: HttpServletResponse

Usage:

this.kindeClientSDK.logout(response);

Returns the raw access token from URL after logged from Kinde.

Arguments:

response?: HttpServletResponse
request?: HttpServletRequest

Usage:

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"

];

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

Arguments:

response?: HttpServletResponse
additionalParameters?: Map<String, Object> //org_name -> String

Usage:

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_credentials

Gets a claim from an access or ID token.

Arguments:

request?: HttpServletRequest,
claim: string, tokenKey?: string

Usage:

kindeClientSDK.getClaim(request,"given_name", "id_token");

Sample output:

David

Returns the state of a given permission.

Arguments:

request?: HttpServletRequest,
key: string

Usage:

this.kindeClientSDK.getPermission(request,"read:todos");

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.

Arguments:

request?: HttpServletRequest

Usage:

this.kindeClientSDK.getPermissions(request);

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.

Arguments:

request?: HttpServletRequest

Usage:

this.kindeClientSDK.getOrganization(request);

Sample output:

[
	"orgCode" => "org_1234"
];

getUserDetails

Link to this section

Returns the profile for the current user.

Arguments:

request?: HttpServletRequest

Usage:

this.kindeClientSDK.getUserDetails(request);

Sample output:

[
	"given_name" => "Dave",
	"id" => "abcdef",
	"family_name" => "Smith",
	"email" => "mailto:dave@smith.com"
];

getUserOrganizations

Link to this section

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

Arguments:

request?: HttpServletRequest

Usage:

this.kindeClientSDK.getUserOrganizations(request);

Sample output:

[
	"orgCodes" => ["org_8de8711f46a", "org_820c0f318de"]
];

Gets a feature flag from an access token.

Arguments:

request?: HttpServletRequest,
flagName: string, options?: Map<String, Object> //"defaultValue" => any

Usage:

this.kindeClientSDK.getFlag(request,"is_dark_mode");

Sample output:

[
	"code": "is_dark_mode",
	"type": "boolean",
	"value": true,
	"is_default": false
];

getBooleanFlag

Link to this section

Gets a boolean feature flag from an access token.

Arguments:

request?: HttpServletRequest,
flagName: string, defaultValue?: boolean

Usage:

kindeClientSDK.getBooleanFlag(request,"is_dark_mode", false);

Sample output:

[
	"code": "is_dark_mode",
	"type": "boolean",
	"value": false,
	"is_default": true
];

Gets a string feature flag from an access token.

Arguments:

request?: HttpServletRequest,
flagName: string, defaultValue?: string

Usage:

kindeClientSDK.getStringFlag(request,"theme");

Sample output:

[
	"code": "theme",
	"type": "string",
	"value": "black",
	"is_default": false
];

getIntegerFlag

Link to this section

Gets a integer feature flag from an access token.

Arguments:

request?: HttpServletRequest,
flagName: string, defaultValue?: integer

Usage:

kindeClientSDK.getIntegerFlag(request,"competitions_limit");

Sample output:

[
	"code": "competitions_limit",
	"type": "integer",
	"value": 1,
	"is_default": false
];

isAuthenticated

Link to this section

To check user authenticated or not.

Arguments:

response?: HttpServletResponse
request?: HttpServletRequest

Usage:

this.kindeClientSDK.isAuthenticated(request, response);

Sample output:

true or false

If you need help connecting to Kinde, please 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