Translate SlashID tokens to existing tokens
Context
With Gate, you can authenticate your users with SlashID token and propagate tokens in your existing format to your services.
This guide assumes your current system supports authentication based on any request header or cookies.
This is the reverse operation of Translate legacy tokens to SlashID tokens.
Solution
To execute such migration, you can use Token downgrade plugin.
Most of the functionality is provided by Gate out of the box. You need only to implement a token generation endpoint.
Step 1 - Run Gate in transparent mode.
You should also have Gate deployed in your infrastructure. You should run Gate in transparent mode to ensure that everything was correctly deployed.
Step 2 - Implement token generation endpoint
Gate needs to obtain an existing token based on the SlashID person id or handles.
Gate sends a POST
request to your endpoint with a payload like the example below:
{
"slashid_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6Ik..."
"person_id": "cc006198-ef43-42ac-9b5a-b52713569d0f",
"handles": [
{
"type": "email_address",
"value": "user@user.com"
},
{
"type": "phone_number",
"value": "+447975777666"
}
]
}
Please note the plugin includes handles
in the request to the token minting endpoint only if the retrieve_handles
parameter is set to true
.
Example of a valid response from your endpoint:
{
"headers_to_set": {
"Authorization": "Basic YWxhZGRpbjpvcGVuc2VzYW1l",
"IsLegacyHeader": "true"
},
"cookies_to_add": {
"X-Internal-Auth": "9xUromfTraIwHpmC6R9NDwJwItE"
}
}
Gate will remove the SlashID token in the Authorization
header, will override the original request headers with the headers returned from headers_to_set
, and will add all cookies from cookies_to_add
to the ones already present in the request.
Step 3 - Enable the token-translation-downgrade
plugin
Now you can now enable the token-translation-downgrade
plugin.
The plugin doesn't do anything if a SlashID token is not provided.
The full plugin configuration reference can be found on the Token downgrade plugin page.
- Environment variables
- HCL
- JSON
- TOML
- YAML
GATE_PLUGINS_<PLUGIN NUMBER>_NAME=token-translation-downgrade
GATE_PLUGINS_<PLUGIN NUMBER>_CONFIGURATION_TOKEN_GENERATE_ENDPOINT=<Token generate endpoint>
GATE_PLUGINS_<PLUGIN NUMBER>_CONFIGURATION_SLASHID_ORG_ID=<SlashID Org ID>
GATE_PLUGINS_<PLUGIN NUMBER>_CONFIGURATION_SLASHID_API_KEY=<SlashID API key>
In Environment variables configuration, <PLUGIN NUMBER>
defined plugin execution order.
gate = {
plugins = [
// ...
{
name = "token-translation-downgrade"
configuration = {
token_generate_endpoint = "<Token generate endpoint>"
slashid_org_id = "<SlashID Org ID>"
slashid_api_key = "<SlashID API key>"
}
}
// ...
]
}
{
"gate": {
"plugins": [
// ...
{
"name": "token-translation-downgrade",
"configuration": {
"token_generate_endpoint": "<Token generate endpoint>",
"slashid_org_id": "<SlashID Org ID>",
"slashid_api_key": "<SlashID API key>"
}
}
// ...
]
}
}
[[gate.plugins]]
name = "token-translation-downgrade"
configuration.token_generate_endpoint = "<Token generate endpoint>"
configuration.slashid_org_id = "<SlashID Org ID>"
configuration.slashid_api_key = "<SlashID API key>"
gate:
plugins:
// ...
- name: token-translation-downgrade
configuration:
token_generate_endpoint: <Token generate endpoint>
slashid_org_id: <SlashID Org ID>
slashid_api_key: <SlashID API key>
// ...
Step 4 - Authenticate users with SlashID
Now you can modify your frontend application to authenticate users with SlashID (for example via the Identity Management SDK).
When requests with SlashID token reach Gate they it will be mapped to your old configuration format.
Example
Configuring Gate
This is an example configuration for Gate such that the */api/admin
endpoint receives a legacy token in addition to the SlashID one.
Note how the translator plugin invokes the webhook at http://backend:8000/mint_token
to mint a legacy token given the SlashID token.
slashid_config: &slashid_config
slashid_org_id: { { .env.SLASHID_ORG_ID } }
slashid_api_key: { { .env.SLASHID_API_KEY } }
slashid_base_url: { { .env.SLASHID_BASE_URL } }
gate:
port: 8080
log:
format: text
level: trace
default:
target: http://backend:8000
plugins:
- id: translator_down
type: token-translation-downgrade
enable_http_caching: true
enabled: false
parameters:
<<: *slashid_config
mint_token_endpoint: http://backend:8000/mint_token
urls:
# The /api/admin endpoint receives a second internal token type as translated by the
# 'translator_down' plugin
- pattern: "*/api/admin"
target: http://backend:8000
plugins:
translator_down:
enabled: true
An example token minting endpoint
For this example, the incoming token is a standard SlashID JWT token:
{
"authenticated_methods": [
"webauthn"
],
"exp": 3361923081,
"first_token": true,
"groups": [],
"iat": 1680959743,
"iss": "https://sandbox.slashid.dev",
"jti": "b28f8e3e6d5d38cb243912056a9f7502",
"oid": "f978a6bd-3e45-bcda-cb4e-573d0bad155b",
"identifier": "user@example.com"
"person_id": "pid:01975547e4245aa5ae45d4b554ad03d173807e99f22195952a940ee12f6d9c0781c953e026:2"
}
The mint_token
extracts the handle/username from the SlashID token, looks up
the corresponding token in the legacy IdP and mints a new legacy token.
def get_user(username: Optional[str]) -> Optional[DatabaseUser]:
try:
if username == f"user@{vendor_domain}":
return DatabaseUser(
username=username,
name=f"Regular {vendor_name} User",
street_address="1234 Main Street, Apartment 101, Manvel 77578, Texas, USA",
password_hash=pbkdf2_sha256.hash(username),
user_roles=["user"],
)
if username == f"admin@{vendor_domain}":
return DatabaseUser(
username=username,
name=f"Admin {vendor_name} User",
street_address="666 Greenwich Street, New York 10009, New York, USA",
password_hash=pbkdf2_sha256.hash(username),
user_roles=["user", "admin"],
)
except:
pass
return None
def mint_token(request: MintTokenRequest) -> MintTokenResponse:
logger.info(f"/mint_token: request={request}")
sid_token = request.slashid_token
try:
token = jwt.decode(sid_token, options={"verify_signature": False})
except Exception as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Token {sid_token} is invalid: {e}",
)
username = sid_token.get("identifier")
user = get_user(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"User {user} is not mapped to SlashID person",
)
new_token = jwt.encode(
{
"typ": "internal_token_format",
"username": username,
"name": user.name,
},
TOKEN_SIGNING_KEY,
algorithm="HS256",
)
return MintTokenResponse(
headers_to_set={"Authorization": "Bearer " + new_token}, cookies_to_add=None
)
Conclusion
Your frontend has gracefully switched over to SlashID without changing your backend identity logic.