JWT SSO
Although OAuth2 is the most recommended way of authenticating your members to community, JWT SSO is the easiest way if your website does not support OAuth2. You should be able to implement it by adding a few lines of code to your website or product.
JWT SSO is usually the preferred method when you want to embed your community in your product, marketing website, or phone app using IFrame or WebView.
In this method, you'll sign a JSON Web Token (JWT) with User's information using a private key. Then the generated token should be passed as token
in the query string to Bettermode.
To generate the JWT token, first you need to get your Single Sign-On private key. Login to your community as an Admin. In the Administration page under Authentication, enable the "JWT SSO" and you should find the Single Sign-On private key there.
Next, you should first install a JWT library:
- Node.js
- Python
- Ruby
- Java
- PHP
npm install --save jsonwebtoken
pip install PyJWT
sudo gem install jwt
# See instructions here:
https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.7.0
composer require firebase/php-jwt
Then use the following source code and the SSO private key inside the Bettermode "JWT SSO" section in Administration page, to generate the JWT token:
- Node.js
- Python
- Ruby
- Java
- PHP
const jwt = require("jsonwebtoken");
const privateKey = "{Your Private Key}";
function createToken(user) {
const userData = {
sub: user.id, // user's ID in your product
email: user.email,
name: user.name,
tagline: user.tagline, // optional
iat: Math.round(new Date().getTime() / 1000), // token issue time
exp: Math.round(new Date().getTime() / 1000) + 60, // token expiration time
};
return jwt.sign(userData, privateKey, { algorithm: "HS256" });
}
import jwt
private_key = '{Your Private Key}'
def create_token(user):
user_data = {
'sub': user.id, # user's ID in your product
'email': user.email,
'name': user.name,
'tagline': user.tagline, # optional
}
return jwt.encode(user_data, private_key, algorithm='HS256')
require 'jwt'
privateKey = '{Your Private Key}'
def createToken(user)
userData = {
:sub => user[:id], # user's ID in your product
:email => user[:email],
:name => user[:name],
:tagline => user[:tagline], # optional
}
JWT.encode(userData, privateKey, 'HS256')
end
import java.util.HashMap;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class TokenCreator {
private static final String privateKey = "{Your Private Key}";
public static String createToken(User user) throws Exception {
HashMap<String, Object> userData = new HashMap<String, Object>();
userData.put("sub", user.id); // user's ID in your product
userData.put("email", user.email);
userData.put("name", user.name);
userData.put("tagline", user.tagline); // optional
return Jwts.builder()
.setClaims(userData)
.signWith(SignatureAlgorithm.HS256, privateKey.getBytes("UTF-8"))
.compact();
}
}
use FirebaseJWTJWT;
const privateKey = '{Your Private Key}';
function createToken($user) {
$userData = [
'sub' => $user['id'], // user's ID in your product
'email' => $user['email'],
'name' => $user['name'],
'tagline' => $user['tagline'], // optional
];
return JWT::encode($userData, privateKey, 'HS256');
}
Finally, you should pass the generated JWT token to Bettermode as followed:
https://YOUR_COMMUNITY_DOMAIN/api/auth/sso?jwt=[Generated SSO Token]
If the user does not already exist, Bettermode will create the user using the provided information in the JWT and log them in. If the user exists, it will update user's information and log them in.
You can set an optional redirect_uri
query string. If the redirect_uri
is set, user will be redirected to that URL, otherwise, we'll send the user to community home page.
To prevent open redirect attack, your redirect_uri
can only be a relative path starting with /
(e.g. /spaces
).
Additionally, to help preserving user's context, we will append a referrer_uri
to the authorization URL that you provide. You can use the value of this parameter as redirect_uri
to send users to their last location prior to the authentication.
https://YOUR_COMMUNITY_DOMAIN/api/auth/sso?jwt=[Generated SSO Token]&redirect_uri=[Value of referrer_uri]
The redirect_uri
parameter will be ignored if the user is new in the community and the "New users homepage" setting is configured.
Identifying user's existence
Bettermode will first try to find the user using sub
. If the user is found, it will update their information including their email address.
If no user is found based on sub
, it will try to find the user using the email address provided.
Embedding Bettermode pages
One of the most common use-cases of JWT SSO is seamlessly embedding your community or part of it into your product, marketing website, or mobile app. You can learn more here.
Supported JWT keys
Bettermode JWT SSO supports standard JWT fields. Here you can find all fields supported in the JWT:
sub
(required): The ID of the user in your product or platform. This value will be stored asexternalId
on Bettermode. It should be 50 characters or less.name
(required): The name of the user.email
(required): The email of the user. This email address is considered as a verified address. You should make sure you've verified it on your side. It should be 200 characters or less.tagline
: The short bio of the user in plain text format.picture
: A full URL to user's profile picture. It should include https:// or http://. Bettermode will not validate this field, so you should make sure it's a valid URL on your side.iat
: The issue time of the JWT.exp
: The expiration time of the JWT. Although this value is not required, it's highly recommended to set it to 60 seconds from now. If it's not set, the token will be valid forever and can introduce security issues.spaceIds
: An array of Bettermode space IDs the user should be joined to.spaces
: An array of objects in the following format:
[
{
"spaceId": "{bettermodeSpaceId}",
"role": "MEMBER", // Can be "MEMBER"
"action": "JOIN", // Can be "JOIN" or "LEAVE"
}
]
For most use-cases regarding joining spaces, you should use spaceIds
array instead of spaces
array.
spaces
array should be used when you need more customization.
spacesOperation
: When you passspaces
orspaceIds
, you can set this field to"REPLACE"
or"ADD"
. If you set it to"REPLACE"
, the user will be removed from all spaces before joining the new spaces. If you set it to"ADD"
, the user will be added to the new spaces, without being removed form the spaces they already have access to. The "REPLACE" is a great solution when you want to enforce users to be part of specific spaces. For example, you can setspacesOperation
to"REPLACE"
andspaceIds
to["spaceId1", "spaceId2"]
to make sure the user is only part of those two spaces.
spacesOperation
is not required. If you don't set it, the user will be added to the new spaces. (In the case of passing spaces
the action
field is the determinant behaviour).
Custom Fields
Any other fields you include in the payload will be processed as potential custom fields. You should first set externalKeys
for your custom fields under Administration > Profile Fields > {Your custom field} > Advanced Options > External Keys.
External keys are used to map the additional fields you passed in the JWT payload, to your custom profile field. The first key with a non-empty value will be used.
Settings
You can add these optional settings under Adminstration > Settings > Authentication > JWT SSO > Settings:
Login Button Text
When you enabled JWT SSO in your community, you can update the community's login button name by adding a value for this setting.
Authorization URL
This is the URL that users should be redirected to when they click the community login button.
If there's only one SSO method enabled for your community authentication, the users will be automatically redirect to the "Authorization URL" you have set.
Sign Up URL
This is the URL that users should be redirected to when they click the community sign up button.
Logout URL
When the user logs out of from the community, most likely, you want them to be logged out from your identity management system as well. Otherwise, next time they click on the community login button, after being redirected to your identity management system authorization URL, the user will be logged in to the community right away without asking for username and password because they're not logged out of the identity management system yet.
To fix this, you can provide a Logout URL. This way, after the users click on Sign out button in Bettermode, they will be redirected to the Logout URL provided and they will be logged out from the identity management system as well.
Account Settings URL
Your community members can change their email inside Bettermode by default. Most likely, there is also a section on your identity management system where users can change their emails. This will cause confusion for your members since they can update their information in two different places and they're not synced by default.
For instance, a member with "[email protected]" email can update their email address in the community to "[email protected]". Unless you're using Bettermode Webhooks, API, or Zapier the email of this user on your identity management system is still "[email protected]". In some cases, this might be the right behavior and you want to let members have a different email addresses in the community. But in many cases, you want the emails to be synced and in this case, the best solution is to let them change their information only inside your identity management system.
To do so, you can simply enter a link to your website's account setting URL. If this link is provided, Bettermode won't let members change their email right inside the community. Instead they will see a link next to their email that takes them to your website's account setting. After they change their information on your website, you can use our API or Zapier to update their name right away in the community. Otherwise, their information will be updated the next time they Sign out and login to Bettermode again.
Supported Plans
Authenticating your members using the JWT SSO method is available on the Advanced and Enterprise plans.