The Kinde Elixir SDK allows developers to connect their Elixir app to Kinde.
If you haven’t already got a Kinde account, register for free here (no credit card required).
You also need a Kinde domain to get started, e.g. yourapp.kinde.com
Install erlang and elixir. Update the deps, update path to the SDK in mix.exs
.
{:kinde_sdk, "~> 1.0"}
Add to your extra applications in mix.exs
.
def application do
[
extra_applications: [:logger, :kinde_sdk]
]
end
- In Kinde, go to Settings > Applications > [Your app] > View details.
- Add your callback URLs in the relevant fields. For example:
- Allowed callback URLs (also known as redirect URIs) - for example,
http://localhost:4000/callback
- Allowed logout redirect URLs - for example,
http://localhost:4000
- Allowed callback URLs (also known as redirect URIs) - for example,
- Select Save.
Note: The http://localhost:4000
is an example of a commonly used local development URL. It should be replaced with the URL where your app is running.
Register your first user by signing up yourself. You’ll see your newly registered user on the Users page of the relevant organization in Kinde.
Kinde comes with a production environment, but you can set up other environments if you want to. Note that each environment needs to be set up independently, so you need to use the Environment subdomain in the code block above for those new environments.
You can set your keys in your application configuration. Use config/config.exs
. For example:
config :kinde_sdk,
backend_client_id: "test_x1y2z3a1",
frontend_client_id: "test_a1b2c3d4",
client_secret: "test_112233",
redirect_url: "http://text.com/callback",
domain: "https://test.kinde.com",
logout_redirect_url: "http://text.com/logout"
Optionally, you can also set scope
as well.
config :kinde_sdk,
scope: "email"
You can also set these variables in .env file within your project directory. The following variables need to be replaced in the code snippets below.
KINDE_HOST
- your Kinde domain - e.g.https://yourkindedomain.kinde.com
KINDE_REDIRECT_URL
- your callback url, make sure this URL is under your allowed callback redirect URLs. - e.g.http://localhost:4000/callback
KINDE_POST_LOGOUT_REDIRECT_URL
- where you want users to be redirected to after logging out, make sure this URL is under your allowed logout redirect URLs. - e.g.http://localhost:4000
KINDE_CLIENT_ID
- you can find this on the Application details pageKINDE_CLIENT_SECRET
- you can find this on the Application details page
After setting .env file you can use System.get_env/1
to retrieve the API key from an environment variables. For example:
config :kinde_sdk,
backend_client_id: System.get_env("KINDE_BACKEND_CLIENT_ID")
Execute following in your terminal to run:
mix deps.get
mix phx.server
The KindeClientSDK struct implements three OAuth flows:
- Client Credentials flow
- Authorization Code flow
- Authorization Code with PKCE flow
Each flow can be used with their corresponding grant type when initializing a client.
OAuth Flow | Grant Type | Type |
---|---|---|
Client Credentials | :client_credentials | atom |
Authorization Code | :authorization_code | atom |
Authorization Code with PKCE | :authorization_code_flow_pkce | atom |
Create a new instance of the Kinde Auth client object before you initialize your app:
{conn, client} =
KindeClientSDK.init(
conn,
Application.get_env(:kinde_sdk, :domain),
Application.get_env(:kinde_sdk, :redirect_url),
Application.get_env(:kinde_sdk, :backend_client_id),
Application.get_env(:kinde_sdk, :client_secret),
:client_credentials,
Application.get_env(:kinde_sdk, :logout_redirect_url)
)
The Kinde client provides methods for easy login / registration.
You can add buttons in your HTML as follows:
<div class="navigation">
<a href="/login" type="button">Login</a>
<a href="/register" type="button">Register</a>
</div>
You will also need to route /login
and /register
to the SDK methods:
conn = KindeClientSDK.login(conn, client)
conn = KindeClientSDK.register(conn, client)
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 /callback
to call a function to handle this.
def callback(conn, _params) do
{conn, client} = KindeClientSDK.get_token(conn)
data = KindeClientSDK.get_all_data(conn)
end
We use the Kinde helper function to get the tokens generated by login
and get_token
.
data = KindeClientSDK.get_all_data(conn)
IO.inspect(data.login_time_stamp, label: "Login Time Stamp")
Or first calling the get_token
function:
{conn, client} = KindeClientSDK.get_token(conn)
Example of a returned token:
%{
"access_token" => "eyJhbGciOiJSUzI1...",
"expires_in" => 89274,
"scope" => "openid profile email offline",
"token_type" => "bearer"
}
This function returns the user object including Kinde ID. This function reads the information from the id_token
that is returned after successful authentication. If the token is missing the profile please make sure the correct scopes are requested (openid profile email offline
).
KindeClientSDK.get_user_detail(conn)
Note: You need to have already authenticated before you call the API, otherwise an error will occur.
Go to the Users page in Kinde to see who has registered.
There is an additional create_org method which allows an organization to be created. This method calls the current sign-up logic by setting the is_create_org
parameter to true.
To helper function to have a new organization created within your application
conn = KindeClientSDK.create_org(conn, client)
conn = KindeClientSDK.create_org(conn, client)
Kinde has a unique code for every organization. You’ll have to pass this code when creating a client in additional_parameters_org_code
through when you register a new user.
If you want a user to sign into a particular organization, pass this code along with the sign in method.
The Kinde SDK client comes with a logout method.
conn = KindeClientSDK.logout(conn)
Returns whether if a user is logged in by verifying that the access token is still valid.
KindeClientSDK.authenticated?(conn)
We have provided a helper to grab any claim from your id or access tokens, which accepts a key for a token and returns the claim value. Optional argument to define which token to check - The helper defaults to access tokens.
KindeClientSDK.get_claims(conn)
KindeClientSDK.get_claim(conn, "jti", :id_token)
This function will returns claims as follows.
%{
"aud" => [],
"azp" => "",
"exp" => 1649314,
"gty" => ["client_credentials"],
"iat" => 1662914,
"iss" => "https://smith.com",
"jti" => "7fa15b8-086-495-bba-b191b2aa2f",
"scp" => ["openid", "profile", "email", "offline"]
}
When it accepts a key for a token as KindeClientSDK.get_claim(conn, "jti", :id_token)
then returns the claim value as follows.
"57012a4-82ca-41f-8f19-0a5c3cc653"
When a user signs in to an organization the access token your product/application contains a custom claim with an array of permissions for that user.
You can set permissions in your Kinde account. Here’s an example.
"permissions":[
"create:todos",
"update:todos",
"read:todos",
"delete:todos",
"create:tasks",
"update:tasks",
"read:tasks",
"delete:tasks",
]
For more details See Define user permissions.
We provide helper functions to more easily access permissions.
KindeClientSDK.get_permissions(conn)
KindeClientSDK.get_permission(conn, "create:todos")
get_permission
checks the permission value and returns if it is granted or not (i.e. checks if permission key exists in the permissions
claim array) and checks the relevant org code by checking against claim org_code
.
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.
additional_params = %{
audience: "api.yourapp.com"
}
KindeClientSDK.init(
conn,
Application.get_env(:kinde_sdk, :domain),
Application.get_env(:kinde_sdk, :redirect_url),
Application.get_env(:kinde_sdk, :backend_client_id),
Application.get_env(:kinde_sdk, :client_secret),
:authorization_code_flow_pkce,
Application.get_env(:kinde_sdk, :logout_redirect_url),
"openid profile email offline",
additional_params
)
For details on how to connect, see Register an API.
By default the KindeSDK SDK requests the following scopes:
profile
email
offline
openid
You can override this by passing scope into the KindeSDK.
When a user refreshes the page or opens a new tab, the authentication state can be lost. To work around this issue, there are two possible solutions:
- One way to do this is by using cookies to store the authentication token. This can be done by setting an
httpOnly
cookie with the authentication token, which will be sent to the server with every request, allowing the server to maintain the authentication state. - Another option is to use a session store to store the authentication token. Elixir has several session store options available, including using a database, in-memory cache, or distributed cache.
Once one of these solutions is implemented, there is no need for additional action to persist the authentication state.
Once the user has successfully authenticated, you’ll have a JWT and possibly a refresh token that should be stored securely.
Property | Type | Is required | Default | Description |
---|---|---|---|---|
domain | string | Yes | Either your Kinde instance url or your custom domain. e.g https://yourapp.kinde.com/ | |
redirect_url | string | Yes | The url that the user will be returned to after authentication | |
backend_client_id | string | Yes | The id of your backend application | |
frontend_client_id | string | Yes | The id of your frontend application | |
client_secret | string | Yes | The id secret of your application - get this from the Kinde admin area | |
logout_redirect_url | string | Yes | Where your user will be redirected upon logout | |
scope | string | No | openid profile email offline | The scopes to be requested from Kinde |
additional_parameters | map | No | %{} | Additional parameters that will be passed in the authorization request |
additional_parameters_audience | string | No | The audience claim for the JWT | |
additional_parameters_org_name | string | No | The org claim for the JWT | |
additional_parameters_org_code | string | No | The org claim for the JWT |
Property | Description | Arguments | Usage | Sample output |
---|---|---|---|---|
login | Constructs redirect url and sends user to Kinde to sign in | conn , client | KindeClientSDK.login(conn, client) | redirect |
register | Constructs redirect url and sends user to Kinde to sign up | conn , client | KindeClientSDK.register(conn, client) | redirect |
logout | Logs the user out of Kinde | conn | KindeClientSDK.logout(conn) | redirect |
get_token | Returns the raw access token from URL after logged from Kinde | conn | KindeClientSDK.get_token(conn) | eyJhbGciOiJIUzI1.. |
create_org | Constructs redirect url and sends user to Kinde to sign up and create a new org for your business | conn , atom | KindeClientSDK.create_org(conn, client) | redirect |
get_claims | Gets all claims from an access or id token | conn , atom | KindeClientSDK.get_claims(conn) or KindeClientSDK.get_claims(conn, :id_token) | %{"aud" => [], "azp" => "", ...} |
get_claim | Gets a claim from an access or id token | conn , string , atom | KindeClientSDK.get_claim(conn, "jti") or KindeClientSDK.get_claim(conn, "jti", :id_token) | "David" |
get_permissions | Returns the state of a all permissions | conn , atom | KindeClientSDK.get_permissions(conn, :id_token) | %{org_code: 'org_1234', is_granted: true} |
get_permission | Returns the state of a given permission | conn , string | KindeClientSDK.get_permission(conn, "create:users") | %{org_code: 'org_1234', permissions: ['create:todos', 'update:todos', 'read:todos']} |
get_organization | Get details for the organization your user is logged into | conn | KindeClientSDK.get_user_organization(conn) | %{org_code: "org_9d78"} |
get_user_detail | Returns the profile for the current user | conn | KindeClientSDK.get_user_detail(conn) | %{email: "dev@smit.com", family_name: "Smith", given_name: "Dave", id: "kp:abcdef"} |
get_user_organizations | Returns the org code from the user token | conn | KindeClientSDK.get_user_organizations(conn) | %{org_codes: ["org_9d78", "org_aca6c", "org_27e56"]} |
get_cache_pid | Returns the Kinde cache PID from the conn | conn | KindeClientSDK.get_cache_pid(conn) | #PID |
save_kinde_client | Saves the Kinde client created into the conn | conn | KindeClientSDK.save_kinde_client(conn) | :ok |
get_kinde_client | Returns the Kinde client created from the conn | conn | KindeClientSDK.get_kinde_client(conn) | %KindeClientSDK{ cache_pid: #PID<0.123.0>, domain: "abcd.com", redirect_uri: "…http://text.com/callback", logout_redirect_uri: …} |
get_all_data | Returns all the Kinde data (tokens) returned | conn | KindeClientSDK.get_all_data(conn) | %{access_token: "eyJhbGciOiJSU…”, expires_in: 1234, id_token: “abcdedhjshfsjg”,…} |
If you need help connecting to Kinde, please contact us at support@kinde.com.
Developer tools