Signing In as a Client

5 views Markdown

Hand a signed-in session to a client from your own system, without ever holding their password.

Overview

Two endpoints cover the moment a client moves from your system into theirs.

POST /clients/validate Checks whether an email and password belong to a live client account. Your front end collects the credentials; this tells you which client is behind them.
POST /clients/sso Issues a one-time sign-in ticket and the URL that spends it. Send the client there and they arrive already signed in.

Use the second one when you already know who the visitor is: your own portal, a control panel, a desk tool. Use the first only when you are the one asking for the password.

Prerequisites

Scopes Clients/ValidateClient and Clients/CreateClientSsoToken. Grant only the one you use.
Account state The client must be an active member account. Inactive or blocked accounts are refused when the ticket is issued.
HTTPS The ticket travels in a URL. Serve the redirect over HTTPS so it is not readable in transit.

Structure

The ticket is a random secret, not a package of data. Everything the sign-in needs — the expiry and the landing page — is stored on the account, so nothing the caller receives can be edited into a different destination or a longer window.

Auth::createSsoToken() Issues the ticket, stores it encrypted on the account and returns the URL.
Auth::verifySsoToken() Resolves the ticket and consumes it before any session exists.
Auth::ssoLogin() Runs the normal sign-in gate, then opens the session.

Step by Step

Sending a Client In

  1. Your system decides which client this visitor is. Call POST /clients/sso with that client's id. You get back a token, a url and an expires_at.
  2. Redirect the visitor to url straight away. Do not show it as a link for later — the ticket is valid for 60 seconds.
  3. The visitor lands signed in, on their dashboard or on the page you named in destination. The ticket is spent; the same URL will not work twice.

Choosing Where They Land

  1. Leave destination out and the client lands on their dashboard.
  2. Send a route key with its parameters to land deeper: "destination": "services", "destination_values": [128].
  3. An absolute URL works too, as long as it belongs to this installation. Anything pointing elsewhere is dropped and the client lands on the dashboard instead.

Reference

Ticket Request

client_id Required. The client to issue the ticket for. user_id is accepted as its former name.
destination Optional. A route key or an absolute URL inside this installation.
destination_values Optional. Route parameters, when destination is a route key.

How the Ticket Behaves

Single use Spent the moment the URL is opened. A replayed link lands on the sign-in form with an explanatory message.
Sixty seconds Issue it and follow it in one motion.
One per client A new ticket silently retires that client's previous one.
Sign-in gate Account status, country blocking and module vetoes are checked when the ticket is spent, as for a password sign-in.

Full field tables, error codes and response shapes are in the endpoint reference for clients.

Example

Issue a ticket and redirect
curl -X POST 'https://panel.example.com/api/v1/admin/clients/sso' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"client_id":64,"destination":"services","destination_values":[128]}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/sso', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    client_id: 64,
    destination: 'services',
    destination_values: [128],
  }),
});

const body = await res.json();

// Redirect from the server that holds the key, not from the visitor's browser:
// the ticket is a credential and should not pass through untrusted code.
res.ok && console.log(body.data.url);
<?php

$ch = curl_init('https://panel.example.com/api/v1/admin/clients/sso');

curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode(['client_id' => 64]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);

// Send the visitor there now; the ticket expires in 60 seconds.
header('Location: ' . $body['data']['url']);
exit;
$response = Api::Clients()->CreateClientSsoToken([
    'client_id'   => 64,
    'destination' => 'services',
    'destination_values' => [128],
]);

// No key, no HTTP round trip — same resource, called in process.
Utility::redirect($response['data']['url']);

Pitfalls

A ticket is a credential. Whoever opens the URL becomes that client. Do not log it, email it or put it in a page a third party can read.

Do not mint tickets in advance. They last 60 seconds and a client holds only one at a time, so issuing a batch leaves you with a single working ticket.

Validate does not sign anyone in. It answers whether a password matches. If you want a session, issue a ticket afterwards.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.