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 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:
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_callback
http://localhost:8080
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.
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.com
http://localhost:8080/api/auth/kinde_callback
http://localhost:8080
KindeClientSDK
struct implements three OAuth flows: client_credentials
, authorization_code
, authorization_code_flow_pkce
Kinde 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 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.
@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 = 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 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 provided
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 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
redirectUri
The URL that the user will be returned to after authentication.
Type: string
Required: yes
clientId
The unique ID of your application. Get this from the Application details section in Kinde.
Type: string
Required: yes
clientSecret
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
grantType
The 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
logoutRedirectUri
Where your user will be redirected when they sign out.
Type: string
Required: yes
scope
The scopes to be requested from Kinde.
Type: string
Required: No
Default: openid profile email offline
additionalParameters
Additional parameters that will be passed in the authorization request.
Type: Map<String, Object>
Required: No
Default: new Hashmap<>()
additionalParameters - audience
The audience claim for the JWT.
Type: string
Required: No
login
Constructs redirect URL and sends user to Kinde to sign in.
Arguments:
response?: HttpServletResponseadditionalParameters?: Map<String, Object> //org_code -> String
Usage:
kindeClientSDK.login(response);
register
Constructs redirect url and sends user to Kinde to sign up.
Arguments:
response?: HttpServletResponseadditionalParameters?: Map<String, Object> //org_code -> String
Usage:
kindeClientSDK.register(response);
logout
Logs the user out of Kinde.
Arguments:
response?: HttpServletResponse
Usage:
this.kindeClientSDK.logout(response);
getToken
Returns the raw access token from URL after logged from Kinde.
Arguments:
response?: HttpServletResponserequest?: 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"
];
createOrg
Constructs 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 -> 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
getClaim
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
getPermission
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
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
Get details for the organization your user is logged into.
Arguments:
request?: HttpServletRequest
Usage:
this.kindeClientSDK.getOrganization(request);
Sample output:
[ "orgCode" => "org_1234"];
getUserDetails
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
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"]];
getFlag
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
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];
getStringFlag
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
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
To check user authenticated or not.
Arguments:
response?: HttpServletResponserequest?: 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.