Skip to main content

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.

UserYour systemLoad balancerToken generate endpointDestination endpointGateSlashID person IDand handlesHTTP request headersto overrideHTTP requestwith SlashID JWTHTTP requestHTTP request withoverriden headers

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.

note

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.

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.

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.