Connect Your Own Login System

Overview

If your website or app already has its own sign-up and login, for example a SaaS product built with Laravel, Rails or Node.js, you keep it. UniSignIn connects to it so every user has one profile with their account details, their data from your system and what they do in your app.

There are three parts:

  1. Log users in: when a user logs into your app, log them into UniSignIn with an SSO token. UniSignIn creates the user on first login and links their earlier anonymous visits to the profile. The token can carry the user's profile, such as plan, role or company.
  2. Push profile data: send data from your servers, for changes made while a user isn't logged in and to load all your existing users.
  3. Track activity: send events and tags from your pages for the actions that matter, such as creating a project or inviting a teammate.

You can then use all of it in Segments, Orchestration, Experiences and Data Exchange exports.

Before you start

  1. Install the UniSignIn tag on the pages of your app, including the pages behind login.
  2. Create an SSO Key Pair in Connect > SSO Token. You get an SSO_PUBLIC_KEY and an SSO_PRIVATE_KEY.
  3. Create an API key in Connect > API Keys and copy its Secret Key, see API Keys.

Keep the SSO private key and the API Secret Key on your server only. Never put them in page code or an app.

Step 1: Log users into UniSignIn

Create the SSO token on your server

The token is your user's email and profile, signed with your SSO private key and the current time, see SSO for the format. A token is valid for 10 minutes, so create it when you render the page. In PHP:

function unisigninSSOToken(string $email, array $profile, string $registeredAt): string
{
    $ts = time();
    $profileJson = json_encode($profile);

    return base64_encode(json_encode([
        'email' => $email,
        'ts' => $ts,
        'profile' => $profileJson,
        'reg_date' => $registeredAt,
        'public_key' => getenv('UNISIGNIN_SSO_PUBLIC_KEY'),
        'signature' => hash('sha512', $email . '-' . $ts . '-' . hash('sha256', $profileJson) . '-' . getenv('UNISIGNIN_SSO_PRIVATE_KEY')),
    ]));
}

$token = unisigninSSOToken($user->email, [
    'first_name' => $user->first_name,
    'last_name' => $user->last_name,
    'plan' => $user->plan,
    'company' => $user->company,
], $user->created_at->toIso8601String());

Each login updates the profile fields in profile. Add custom fields first in Data Management > User Data > Data Schema, see Profile Fields.

Log in on the page

On pages where your user is logged in, check whether they are also logged into UniSignIn, and log them in if not. To update their profile when it changes in your system, log them in again:

window.unisignin = window.unisignin || {}
window.unisignin.cmd = window.unisignin.cmd || []

window.unisignin.cmd.push([
  'getUser',
  function (user) {
    if (!user.isLogin || localStorage.getItem('unisignin_sso') !== 'SSO_VERSION') {
      window.unisignin.cmd.push([
        'sso_login',
        'SSO_TOKEN',
        function (result) {
          if (result.status) localStorage.setItem('unisignin_sso', 'SSO_VERSION')
        },
      ])
    }
  },
])

Replace SSO_TOKEN with the token from your server, and SSO_VERSION with a hash of the user's email and profile, so they log in again when either changes.

The first time a user logs in, UniSignIn creates their account with the email in the token. Users are matched by email, so use the same email as in your system. Add reg_date, the date the user registered in your system, so their UniSignIn registration date matches your records. Use an ISO 8601 date with a time zone, e.g. 2024-05-01T09:30:00Z, or a Unix timestamp.

Log out

When the user logs out of your app, log them out of UniSignIn too:

window.unisignin.cmd.push(['client_logout'])

Step 2: Push profile data from your server

The profile in the SSO token is updated when the user logs in. To update users who aren't logged in, such as after a billing change, or to load all your existing users, push the data from your server.

Set up the import

  1. Go to Data Management > Data Exchange and click New.
  2. Set Exchange Type to Import Data.
  3. In Link identifiers, enter the name of the email field in your data, e.g. email, and choose email as the UniSignIn field.
  4. In Map data fields, map each field you send to a UniSignIn profile field, e.g. plan to Plan. Map the date the user registered in your system, e.g. created_at, to Registration Date. It is used when the user is created in UniSignIn and doesn't change existing users. Add custom fields first in Data Management > User Data > Data Schema if you need them, see Profile Fields.
  5. Turn on Enable, save, and copy the exchange ID.

Send the data

Send one user or a list of up to 100 users per request:

curl -X POST https://secure.signinservice.com/api/v1/user/EXCHANGE_ID \
  -H "Authorization: Bearer SECRET_KEY" \
  -H "x-license: YOUR_LICENSE" \
  -H "Content-Type: application/json" \
  -d '[{"email": "user@unisignin.com", "created_at": "2024-05-01T09:30:00Z", "plan": "pro", "role": "admin", "company": "Acme"}]'

The response tells you how many users were updated and how many were new:

{ "status": 1, "updated": 1, "created": 0 }
  • A user who doesn't exist in UniSignIn yet is created, so you can push all your existing users before they next log in.
  • Only the fields you send are changed. Fields you leave out keep their current values.
  • The data can be used in segments shortly after it is sent.

Send data whenever it changes in your system, e.g. when a user upgrades their plan or changes their role. To load your existing users, send them in batches of 100.

Step 3: Track activity in your app

Send an event when a user does something you want to segment or report on:

window.unisignin.cmd.push(['trace', 'project_created'])
window.unisignin.cmd.push(['trace', 'teammate_invited'])

Use short lowercase names. Events are then available in segments with Actions (last 30 days) contains project_created.

To label a user, add a tag:

window.unisignin.cmd.push(['tag', 'power-user'])

Tags appear in the Tags (last 30 days) profile field. See Events and the JavaScript API.

Example: Laravel

Share the SSO token with your layout for logged-in users:

// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\View;

public function boot(): void
{
    View::composer('layouts.app', function ($view) {
        $user = auth()->user();
        if (! $user) {
            return $view->with('unisigninToken', null);
        }
        $ts = time();
        $profile = json_encode([
            'full_name' => $user->name,
            'plan' => $user->plan,
            'company' => $user->team?->name,
        ]);
        $view->with('unisigninVersion', hash('sha256', $user->email . $profile));
        $view->with('unisigninToken', base64_encode(json_encode([
            'email' => $user->email,
            'ts' => $ts,
            'profile' => $profile,
            'reg_date' => $user->created_at->toIso8601String(),
            'public_key' => config('services.unisignin.sso_public'),
            'signature' => hash('sha512', $user->email . '-' . $ts . '-' . hash('sha256', $profile) . '-' . config('services.unisignin.sso_private')),
        ])));
    });
}
{{-- resources/views/layouts/app.blade.php --}}
@if ($unisigninToken)
<script>
window.unisignin = window.unisignin || {};
window.unisignin.cmd = window.unisignin.cmd || [];
window.unisignin.cmd.push(['getUser', function (user) {
  if (!user.isLogin || localStorage.getItem('unisignin_sso') !== @json($unisigninVersion)) {
    window.unisignin.cmd.push(['sso_login', @json($unisigninToken), function (result) {
      if (result.status) localStorage.setItem('unisignin_sso', @json($unisigninVersion));
    }]);
  }
}]);
</script>
@endif

Push profile data when a user changes, from a queued job:

// app/Jobs/SyncUserToUniSignIn.php
use Illuminate\Support\Facades\Http;

public function handle(): void
{
    Http::withToken(config('services.unisignin.secret'))
        ->withHeaders(['x-license' => config('services.unisignin.license')])
        ->post('https://secure.signinservice.com/api/v1/user/' . config('services.unisignin.exchange_id'), [[
            'email' => $this->user->email,
            'created_at' => $this->user->created_at->toIso8601String(),
            'plan' => $this->user->plan,
            'role' => $this->user->role,
            'company' => $this->user->team?->name,
        ]])
        ->throw();
}
// app/Observers/UserObserver.php
public function saved(User $user): void
{
    SyncUserToUniSignIn::dispatch($user);
}

Load existing users once with a console command:

User::query()->chunk(100, function ($users) {
    Http::withToken(config('services.unisignin.secret'))
        ->withHeaders(['x-license' => config('services.unisignin.license')])
        ->post('https://secure.signinservice.com/api/v1/user/' . config('services.unisignin.exchange_id'),
            $users->map(fn ($user) => ['email' => $user->email, 'created_at' => $user->created_at->toIso8601String(), 'plan' => $user->plan])->all());
});

Log out of UniSignIn on your logout page or button:

window.unisignin.cmd.push(['client_logout'])

Stay ahead in first-party data

Identity, consent, audience, and subscription insights for publishers. A few emails a month, no spam.

By subscribing you agree to our privacy policy.