Skip to content
This page View Markdown Open in ChatGPT Open in Claude

Custom auth provider

The code in every language

Each example is the inside of main, written in every language API Maker runs : pick yours in the tabs, and every page shows it from then on. The types a call takes (GetAll, DeepItem…) are those of am : from am import GetAll in Python, use am::GetAll; in Rust, import am.GetAll; in Java and Kotlin ; PHP and C# have them without an import, Go names them am.GetAll.

Custom authorization

To get the value of request header x-custom-authorization, use g.req.auth.authCustom.

const customAuth = g.req.auth.authCustom; // what the token validator returned
custom_auth = g.req.auth.authCustom  # what the token validator returned
$customAuth = $g->req->auth->authCustom; // what the token validator returned
var customAuth = g.Req.Auth["authCustom"]; // what the token validator returned
customAuth := g.Req.Auth().Get("authCustom") // what the token validator returned
let custom_auth = &g.req.auth()["authCustom"]; // what the token validator returned
Object customAuth = g.req.getAuth().get("authCustom"); // what the token validator returned
val customAuth = g.req.auth["authCustom"] // what the token validator returned

Whatever your Token Validator function returns is what lands in g.req.auth.authCustom. Return the decoded JWT payload and you get the decoded token there — return your own object and you get that instead.

Why a custom provider?

AM, database, AWS, Azure and Google cover most cases. But sometimes the token is yours — your own claims, your own signing key, your own expiry rules. That is what a Custom Token Generator is for: you write the code that mints the token and the code that checks it, and API Maker handles the rest of the request pipeline.

Below is a complete working example: a JWT signed with HS256, with the usual JWT package of each language.


1. Add a JWT package, and a signing key

Go to Operations → Sandbox Settings, select the language of the provider and add its package (see Packages):

Language Registry Package
TypeScript npm jose
Python PyPI pyjwt
PHP Packagist firebase/php-jwt
C# NuGet Microsoft.IdentityModel.JsonWebTokens
Go Go modules github.com/golang-jwt/jwt/v5
Rust crates.io jsonwebtoken[rust_crypto] and uuid[v4]
Java, Kotlin Maven Central com.auth0:java-jwt

Once added, it is available in every piece of code of that language, including the two functions below.

Then add a key of your own to the secret, jwtSigningKey, with at least 32 random characters: the packages of PHP and C# refuse a shorter key for HS256. Keep it for the tokens alone. common.secretFETransfer is no signing key: your apps hold it, so anyone with a copy of an app could sign tokens with it.


2. Create the auth provider

Go to Security → Auth Providers, click + and pick Custom Token Generator.

You will get three tabs: Basic Info, Token Generator and Token Validator. The Basic Info is always TypeScript, which API Maker runs itself when you save ; the generator and the validator are written in the language you pick for the provider.

Basic Info

1
2
3
4
5
6
7
import * as T from 'types';

let customTokenBasicInfo: Partial<T.IAuthTokenAMDB> = {
    name: 'jose_example',
    runOnNativeProcess: false,
};
module.exports = customTokenBasicInfo;
  • name is how you will refer to this provider everywhere else — in the get-token request and in the authProviders array of your settings files.
  • Keep runOnNativeProcess as false so the code runs inside the sandbox, where the packages of its language live.

Token Generator

Runs when someone asks for a token. Whatever you return is sent back to the caller as-is.

Token Generator
import * as T from 'types';
import { SignJWT } from "jose";

// Produce and return a token string from the request context.
// Runs at token-generation time.
async function main(g: T.IAMGlobal) {
    const body: { role: string, userId: string } = g.req.body;
    const signingKey = await g.sys.system.getSecret('jwtSigningKey');
    const secret = new TextEncoder().encode(signingKey);

    const accessToken = await new SignJWT({
        // set your required data
        role: body.role,
        email: "[email protected]",
        companyId: "cmp_123",
    }).setProtectedHeader({
        alg: "HS256",
        typ: "JWT",
    }).setSubject(body.userId)
        .setIssuedAt()
        .setExpirationTime("15m")
        .setJti(crypto.randomUUID())
        .sign(secret);

    return {
        access_token: accessToken,
        refresh_token: 'refresh_token_test',
        validity: 19200
    };
};
module.exports = main;
Token Generator
import time
import uuid

import jwt  # the package pyjwt

from am import G


# Produce and return a token string from the request context.
# Runs at token-generation time.
async def main(g: G):
    body = g.req.body
    signing_key = await g.sys.system.get_secret('jwtSigningKey')

    now = int(time.time())
    access_token = jwt.encode(
        {
            # set your required data
            'role': body.role,
            'email': '[email protected]',
            'companyId': 'cmp_123',
            'sub': body.userId,
            'iat': now,
            'exp': now + 15 * 60,
            'jti': str(uuid.uuid4()),
        },
        signing_key,
        algorithm='HS256',
    )

    return {
        'access_token': access_token,
        'refresh_token': 'refresh_token_test',
        'validity': 19200,
    }
Token Generator
<?php
use Firebase\JWT\JWT;

// Produce and return a token string from the request context.
// Runs at token-generation time.
function main(AmG $g)
{
    $body = $g->req->body;
    $signingKey = $g->sys->system->getSecret('jwtSigningKey');

    $now = time();
    $accessToken = JWT::encode([
        // set your required data
        'role' => $body->role,
        'email' => '[email protected]',
        'companyId' => 'cmp_123',
        'sub' => $body->userId,
        'iat' => $now,
        'exp' => $now + 15 * 60,
        'jti' => bin2hex(random_bytes(16)),
    ], $signingKey, 'HS256');

    return [
        'access_token' => $accessToken,
        'refresh_token' => 'refresh_token_test',
        'validity' => 19200,
    ];
}
Token Generator
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

file static class Api
{
    // Produce and return a token string from the request context.
    // Runs at token-generation time.
    public static async Task<object?> Main(G g)
    {
        var body = g.Req.Body;
        var signingKey = (await g.Sys.System.GetSecret("jwtSigningKey"))?.GetValue<string>() ?? "";

        var accessToken = new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor
        {
            // set your required data
            Claims = new Dictionary<string, object>
            {
                ["role"] = body.GetString("role") ?? "",
                ["email"] = "[email protected]",
                ["companyId"] = "cmp_123",
                ["sub"] = body.GetString("userId") ?? "",
                ["jti"] = Guid.NewGuid().ToString(),
            },
            IssuedAt = DateTime.UtcNow,
            Expires = DateTime.UtcNow.AddMinutes(15),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)), SecurityAlgorithms.HmacSha256),
        });

        return new
        {
            access_token = accessToken,
            refresh_token = "refresh_token_test",
            validity = 19200,
        };
    }
}
Token Generator
package code

import (
    "crypto/rand"
    "encoding/hex"
    "time"

    "apimaker/am"
    "github.com/golang-jwt/jwt/v5"
)

// Produce and return a token string from the request context.
// Runs at token-generation time.
func Main(g *am.G) (any, error) {
    body := g.Req.Body()
    secret, err := g.Sys.System.GetSecret("jwtSigningKey")
    if err != nil {
        return nil, err
    }
    signingKey, _ := secret.(string)

    jti := make([]byte, 16)
    if _, err := rand.Read(jti); err != nil {
        return nil, err
    }
    now := time.Now()
    accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        // set your required data
        "role":      body.String("role"),
        "email":     "[email protected]",
        "companyId": "cmp_123",
        "sub":       body.String("userId"),
        "iat":       now.Unix(),
        "exp":       now.Add(15 * time.Minute).Unix(),
        "jti":       hex.EncodeToString(jti),
    }).SignedString([]byte(signingKey))
    if err != nil {
        return nil, err
    }

    return am.M{
        "access_token":  accessToken,
        "refresh_token": "refresh_token_test",
        "validity":      19200,
    }, nil
}
Token Generator
use std::time::{SystemTime, UNIX_EPOCH};

use am::{Errors, G, Value, json};
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use uuid::Uuid;

// Produce and return a token string from the request context.
// Runs at token-generation time.
pub fn main(g: &mut G) -> Result<Value, Errors> {
    let body = g.req.body().clone();
    let signing_key = g.sys.system.get_secret("jwtSigningKey", None)?;

    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    let claims = json!({
        // set your required data
        "role": body["role"],
        "email": "[email protected]",
        "companyId": "cmp_123",
        "sub": body["userId"],
        "iat": now,
        "exp": now + 15 * 60,
        "jti": Uuid::new_v4().to_string(),
    });
    let key = EncodingKey::from_secret(signing_key.as_str().unwrap_or_default().as_bytes());
    let access_token = encode(&Header::new(Algorithm::HS256), &claims, &key)?;

    Ok(json!({
        "access_token": access_token,
        "refresh_token": "refresh_token_test",
        "validity": 19200,
    }))
}
Token Generator
import am.G;
import am.JsonObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.time.Instant;
import java.util.UUID;

class Api {
    // Produce and return a token string from the request context.
    // Runs at token-generation time.
    static Object main(G g) {
        JsonObject body = g.req.getBody();
        String signingKey = (String) g.sys.system.getSecret("jwtSigningKey");

        Instant now = Instant.now();
        String accessToken = JWT.create()
            // set your required data
            .withClaim("role", body.getString("role"))
            .withClaim("email", "[email protected]")
            .withClaim("companyId", "cmp_123")
            .withSubject(body.getString("userId"))
            .withIssuedAt(now)
            .withExpiresAt(now.plusSeconds(15 * 60))
            .withJWTId(UUID.randomUUID().toString())
            .sign(Algorithm.HMAC256(signingKey));

        return JsonObject.of(
            "access_token", accessToken,
            "refresh_token", "refresh_token_test",
            "validity", 19200);
    }
}
Token Generator
import am.G
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import java.time.Instant
import java.util.UUID

// Produce and return a token string from the request context.
// Runs at token-generation time.
fun main(g: G): Any? {
    val body = g.req.body
    val signingKey = g.sys.system.getSecret("jwtSigningKey") as String

    val now = Instant.now()
    val accessToken = JWT.create()
        // set your required data
        .withClaim("role", body.getString("role"))
        .withClaim("email", "[email protected]")
        .withClaim("companyId", "cmp_123")
        .withSubject(body.getString("userId"))
        .withIssuedAt(now)
        .withExpiresAt(now.plusSeconds(15 * 60))
        .withJWTId(UUID.randomUUID().toString())
        .sign(Algorithm.HMAC256(signingKey))

    return mapOf(
        "access_token" to accessToken,
        "refresh_token" to "refresh_token_test",
        "validity" to 19200,
    )
}

The whole request body reaches this function as g.req.body, so you decide what your login payload looks like.

Token Validator

Runs on every request that carries the x-custom-authorization header. The token is in g.req.body.token.

Token Validator
import * as T from 'types';
import { jwtVerify } from "jose";

// Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
// Throw or return null/undefined to mark invalid.
async function main(g: T.IAMGlobal) {
    const accessToken = g.req.body.token;
    const signingKey = await g.sys.system.getSecret('jwtSigningKey');
    const secret = new TextEncoder().encode(signingKey);

    const { payload } = await jwtVerify(accessToken, secret, {
        algorithms: ["HS256"],
    });

    return payload;

    // return true; // if truthy value returned, means token is valid.
    // return false; // if token is not valid
    // return {}; // You can return some object also and it means token is valid.
};
module.exports = main;
Token Validator
import jwt  # the package pyjwt

from am import G


# Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
# Raise or return None to mark invalid.
async def main(g: G):
    access_token = g.req.body.token
    signing_key = await g.sys.system.get_secret('jwtSigningKey')

    # Raises when the signature or the expiry is wrong.
    payload = jwt.decode(access_token, signing_key, algorithms=['HS256'])

    return payload
Token Validator
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

// Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
// Throw or return null to mark invalid.
function main(AmG $g)
{
    $accessToken = $g->req->body->token;
    $signingKey = $g->sys->system->getSecret('jwtSigningKey');

    // Throws when the signature or the expiry is wrong.
    $payload = JWT::decode($accessToken, new Key($signingKey, 'HS256'));

    return $payload;
}
Token Validator
using System.Text;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;

file static class Api
{
    // Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
    // Throw or return null to mark invalid.
    public static async Task<object?> Main(G g)
    {
        var accessToken = g.Req.Body.GetString("token");
        var signingKey = (await g.Sys.System.GetSecret("jwtSigningKey"))?.GetValue<string>() ?? "";

        var result = await new JsonWebTokenHandler().ValidateTokenAsync(accessToken, new TokenValidationParameters
        {
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey)),
            ValidAlgorithms = [SecurityAlgorithms.HmacSha256],
            ValidateIssuer = false,
            ValidateAudience = false,
        });

        return result.IsValid ? result.Claims : null;
    }
}
Token Validator
package code

import (
    "apimaker/am"
    "github.com/golang-jwt/jwt/v5"
)

// Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
// Return an error or nil to mark invalid.
func Main(g *am.G) (any, error) {
    accessToken := g.Req.Body().String("token")
    secret, err := g.Sys.System.GetSecret("jwtSigningKey")
    if err != nil {
        return nil, err
    }
    signingKey, _ := secret.(string)

    // An error when the signature or the expiry is wrong.
    payload := jwt.MapClaims{}
    _, err = jwt.ParseWithClaims(accessToken, payload, func(*jwt.Token) (any, error) {
        return []byte(signingKey), nil
    }, jwt.WithValidMethods([]string{"HS256"}))
    if err != nil {
        return nil, err
    }

    return payload, nil
}
Token Validator
use am::{Errors, G, Value};
use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};

// Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
// Return an error or null to mark invalid.
pub fn main(g: &mut G) -> Result<Value, Errors> {
    let access_token = g.req.body()["token"].as_str().unwrap_or_default().to_owned();
    let signing_key = g.sys.system.get_secret("jwtSigningKey", None)?;

    // An error when the signature or the expiry is wrong.
    let key = DecodingKey::from_secret(signing_key.as_str().unwrap_or_default().as_bytes());
    let payload = decode::<Value>(&access_token, &key, &Validation::new(Algorithm::HS256))?;

    Ok(payload.claims)
}
Token Validator
import am.G;
import am.Json;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

class Api {
    // Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
    // Throw or return null to mark invalid.
    static Object main(G g) {
        String accessToken = g.req.getBody().getString("token");
        String signingKey = (String) g.sys.system.getSecret("jwtSigningKey");

        // Throws when the signature or the expiry is wrong.
        var verified = JWT.require(Algorithm.HMAC256(signingKey)).build().verify(accessToken);

        return Json.parse(new String(Base64.getUrlDecoder().decode(verified.getPayload()), StandardCharsets.UTF_8));
    }
}
Token Validator
import am.G
import am.Json
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import java.util.Base64

// Validate an incoming token. Return a truthy object (usually the decoded payload) to mark valid.
// Throw or return null to mark invalid.
fun main(g: G): Any? {
    val accessToken = g.req.body.getString("token")
    val signingKey = g.sys.system.getSecret("jwtSigningKey") as String

    // Throws when the signature or the expiry is wrong.
    val verified = JWT.require(Algorithm.HMAC256(signingKey)).build().verify(accessToken)

    return Json.parse(String(Base64.getUrlDecoder().decode(verified.payload)))
}

The rule is simple — truthy means valid. Anything falsy, or a thrown error, means the request is rejected.


3. Generate a token

Request Method: POST

URL

/api/system-api/user-path/token

1
2
3
4
5
{
    "name": "jose_example",
    "userId": "user_123",
    "role": "admin"
}
  • name tells API Maker which auth provider to run. Everything else in the body is yours and is passed straight to your Token Generator as g.req.body.
  • For more information about this API click here.

Response — exactly what your generator returned:

1
2
3
4
5
{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refresh_token": "refresh_token_test",
    "validity": 19200
}

4. Send the token

Put the access_token in the x-custom-authorization header on every call:

x-custom-authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

To let an API accept this provider, add its name to authProviders in your settings file:

authProviders: <string[]>["jose_example"],

5. Read it in your custom code

import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const auth = g.req.auth.authCustom;

    g.logger.log(auth.sub);        // user_123
    g.logger.log(auth.role);       // admin
    g.logger.log(auth.companyId);  // cmp_123

    return { ok: true };
};
module.exports = main;
1
2
3
4
5
6
7
8
def main(g):
    auth = g.req.auth.authCustom

    g.logger.log(auth.sub)        # user_123
    g.logger.log(auth.role)       # admin
    g.logger.log(auth.companyId)  # cmp_123

    return {'ok': True}
<?php
function main(AmG $g)
{
    $auth = $g->req->auth->authCustom;

    $g->logger->log($auth->sub);        // user_123
    $g->logger->log($auth->role);       // admin
    $g->logger->log($auth->companyId);  // cmp_123

    return ['ok' => true];
}
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var auth = g.Req.Auth.GetObject("authCustom");

        g.Logger.Log(auth.GetString("sub"));        // user_123
        g.Logger.Log(auth.GetString("role"));       // admin
        g.Logger.Log(auth.GetString("companyId"));  // cmp_123

        return new { ok = true };
    }
}
package code

import "apimaker/am"

func Main(g *am.G) (any, error) {
    auth := g.Req.Auth().Map("authCustom")

    g.Logger.Log(auth.String("sub"))       // user_123
    g.Logger.Log(auth.String("role"))      // admin
    g.Logger.Log(auth.String("companyId")) // cmp_123

    return am.M{"ok": true}, nil
}
use am::{Errors, G, Value, json};

pub fn main(g: &mut G) -> Result<Value, Errors> {
    let auth = &g.req.auth()["authCustom"];

    g.logger.log(&auth["sub"]);        // user_123
    g.logger.log(&auth["role"]);       // admin
    g.logger.log(&auth["companyId"]);  // cmp_123

    Ok(json!({ "ok": true }))
}
import am.G;
import am.JsonObject;
import java.util.Map;

class Api {
    static Object main(G g) {
        JsonObject auth = g.req.getAuth().getObject("authCustom");

        g.logger.log(auth.getString("sub"));        // user_123
        g.logger.log(auth.getString("role"));       // admin
        g.logger.log(auth.getString("companyId"));  // cmp_123

        return Map.of("ok", true);
    }
}
import am.G

fun main(g: G): Any? {
    val auth = g.req.auth.getObject("authCustom")

    g.logger.log(auth.getString("sub"))        // user_123
    g.logger.log(auth.getString("role"))       // admin
    g.logger.log(auth.getString("companyId"))  // cmp_123

    return mapOf("ok" to true)
}

By the time your code runs, the token is already verified — API Maker rejects bad ones before they reach you. So g.req.auth.authCustom is safe to trust.