Custom API Configuration

Feature Overview

SAML 2.0 (Security Assertion Markup Language), which is supported by Windows AD, is an open standard created to provide cross-domain single sign-on (SSO). The standard is widely used in SSO implementations. However, integrating with SAML 2.0 requires developers to have a certain level of background knowledge (we recommend reading the documentation), and the cost can be relatively high. To optimize this process, we also provide the Custom API feature.

The custom API is a more streamlined alternative to the SAML standard. Customers can modify the existing SSO API. Following Jodoo's rules, they can also call services, return parameters, and return authenticated user information to Jodoo to complete the account association.

Setting Procedure

Where to Set

1. Log in to Jodoo, and go to Management > Management Tools > Business Settings.

2. Under Business Security, enable SSO and click Settings.

Selecting Configuration Method

On the Configure SSO page, select Custom API.

Configuring IdP Login API

The IdP (Identity Provider) login API refers to the API that allows users to log in to a system based on the necessary content deployed by developers. It is typically used in scenarios where user identity verification is required and access to the system needs to be granted.

In Jodoo's SSO, you can configure the login API based on your business's server settings.

Generating Authentication API Key

The authentication key enables information senders and receivers to encrypt and decrypt data. For the custom API, you can customize the authentication key, or generate a unique key by clicking Generate API Key.

Note:

The authentication key should be consistent with the SECRET in your code.

Configuring Authentication Encryption Algorithm

For Jodoo's SSO, three encryption algorithms are supported. They are HS256, HS384, and HS512. You can choose an appropriate algorithm as needed.

Setting Issuer URL

The issuer URL is used to verify whether the request matches the service backend. If the match is successful, the request can be processed. Otherwise, the request will fail. You can customize the content of the issuer URL. For example, you can set it as Issuer.test.

Note:

If you have set the issuer URL, the content in the Issuer URL needs to be consistent with the issuer in your code.

Configuring IdP Logout API

The IdP Logout API refers to the functionality where, when a member accesses the Single Logout URL of Jodoo, Jodoo not only logs out the current member but also redirects the member to the IdP with logout request parameters.

Note:

1. The IdP can terminate the session with this member to achieve the effect of single logout.

2. The format of single logout request parameters is consistent with authentication request parameters, and additionally includes either the "jti" or "nameId" parameter, excluding the "state" field. The "type" field in the Token is a constant value of "slo_req".

Sample Code

Most programming languages can be achieved by relatively good JWT algorithms. The list of third-party library can be found on this page. Take IdP achieved by Python and Go as an example:

Golang Demo


package main
 
import (
   "fmt"
   "github.com/dgrijalva/jwt-go"
   "log"
   "net/http"
   "time"
)
 
const (
   acs      = "https://www.jiandaoyun.com/sso/custom/5cd91fe50e42834f41b7c6ef/acs"
   issuer   = "com.example"
   username = "angelmsger"
   secret   = "jdy"
)
 
func ValidBody(body jwt.MapClaims) bool {
   return body["iss"] == "com.jiandaoyun" && body["aud"] == issuer && body["type"] == "sso_req"
}
 
func ValidToken(query string) bool {
   token, err := jwt.Parse(query, func(token *jwt.Token) (interface{}, error) {
      if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
         return nil, fmt.Errorf("Unexpected Signing Method: %v ", token.Header["alg"])
      }
      return []byte(secret), nil
   })
   if err != nil {
      return false
   }
   claims, ok := token.Claims.(jwt.MapClaims)
   return ok && token.Valid && ValidBody(claims)
}
 
func GetTokenByUsername(username string) (string, error) {
   now := time.Now()
   token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
      "type":     "sso_res",
      "username": username,
      "iss":      issuer,
      "aud":      "com.jiandaoyun",
      "nbf":      now.Unix(),
      "iat":      now.Unix(),
      "exp":      now.Add(1 * time.Minute).Unix(),
   })
   return token.SignedString([]byte(secret))
}
 
func BuildResponseUri(token string, state string) string {
   target := acs + "?response=" + token
   if state != "" {
      target += "&state=" + state
   }
   return target
}
 
func handler(w http.ResponseWriter, r *http.Request) {
   query := r.URL.Query()
   reqToken := query.Get("request")
   if ok := ValidToken(reqToken); ok {
      if resToken, err := GetTokenByUsername(username); err == nil {
         target := BuildResponseUri(resToken, query.Get("state"))
         http.Redirect(w, r, target, http.StatusSeeOther)
      }
      w.WriteHeader(404)
   }
   w.WriteHeader(404)
}
 
func main() {
   http.HandleFunc("/sso", handler)
   log.Fatal(http.ListenAndServe(":8080", nil))
}

Pyphon Demo

from datetime import datetime, timedelta
from flask import Flask, abort, redirect, request
import jwt
 
from jwt import InvalidTokenError
 
 
class Const:
    ACS = 'https://www.jiandaoyun.com/sso/custom/5cd91fe50e42834f41b7c6ef/acs'
    SECRET = 'jdy'
    ISSUER = 'com.example'
    USERNAME = 'angelmsger'
 
 
app = Flask(__name__)
 
 
def valid_token(query):
    try:
        token = jwt.decode(
            query, Const.SECRET,
            audience=Const.ISSUER,
            issuer='com.jiandaoyun'
        )
        return token.get('type') == 'sso_req'
    except InvalidTokenError:
        return False
 
 
def get_token_from_username(username):
    now = datetime.utcnow()
    return jwt.encode({
        "type": "sso_res",
        'username': username,
        'iss': Const.ISSUER,
        "aud": "com.jiandaoyun",
        "nbf": now,
        "iat": now,
        "exp": now + timedelta(seconds=60),
    }, Const.SECRET, algorithm='HS256').decode('utf-8')
 
 
@app.route('/sso', methods=['GET'])
def handler():
    query = request.args.get('request', default='')
    state = request.args.get('state')
    if valid_token(query):
        token = get_token_from_username(Const.USERNAME)
        stateQuery = "" if not state else f"&state={state}"
        return redirect(f'{Const.ACS}?response={token}{stateQuery}')
    else:
        return abort(404)
 
 
if __name__ == '__main__':
    app.run(port=8080)

Was this information helpful?
Yes
NoNo
Need more help? Contact support