Skip to main content

Sign In on Smart TVs and Other Devices

Introduction

Typing an email address and password is awkward on devices with no keyboard, such as smart TVs, streaming boxes, and game consoles. The User Service Device Authorization flow solves this: the device shows a short code and a web address, the user opens that address on a phone or laptop they already use, signs in there, and approves the device. The device is then signed in, without ever collecting credentials itself.

Two applications work together:

  • The device app (for example, the TV app) is configured as a Native application in the User Service. It starts the flow and is the party that ends up signed in.
  • The activation page is a web page you build (as a separate Web application) where the user enters the code and approves the device. The device points the user to it, and the user opens it on their phone or laptop.

Because both applications belong to the same tenant and environment, they share one user store, so the user who signs in on the phone becomes the user signed in on the device. Both sides use the @axinom/mosaic-user-auth library.

How it works

  1. The device asks the User Service to start a device authorization. It receives a device code (a secret it keeps), a short user code, and a verification URL (your activation page).
  2. The device displays the user code and the verification URL to the user. It can also render the verification URL as a QR code so the user can scan it.
  3. The user opens the verification URL on a second device, signs in with any configured identity provider, reviews what is being authorized, and approves the device (or denies it).
  4. While waiting, the device polls the User Service. Once the user approves, the poll returns a refresh token, the device's long-lived credential.
  5. The device exchanges the refresh token for an access token (the same access token used across the Mosaic end-user APIs), both immediately and whenever it later needs to renew.

Configure the application

Device Authorization is available only for Native applications. On the Native application in the Admin Portal, set the following fields:

  • Device Authorization Flow - enable it.
  • Device Verification URL - the address of your activation page: the page where the user enters the code and approves the device (see On the activation page). The device displays this URL to the user and can render it as a QR code. It is set by an administrator and cannot be supplied by the device (this is what prevents a rogue device from sending users to a page it controls).
  • Device Code TTL (seconds) - how long a code stays valid before the user must start over. Defaults to 900 (15 minutes). Keep it long enough to cover a sign-in that includes reading a verification email or a redirect to an external identity provider.
  • Device Poll Interval (seconds) - the minimum time the device must wait between polls. Defaults to 5.

The activation page is your own Web application. Build it in the same environment (so it shares the user store), point it at the device endpoints as shown in On the activation page, and set its address as the Device Verification URL above.

note

Screenshot placeholder: the Application detail page for a Native application, showing the Device Authorization Flow fields. (To be added.)

On the device

The device app starts the flow, shows the code, then polls until the user approves. On success it receives a refresh token, which it stores and uses to obtain access tokens.

import { useUserService } from '@axinom/mosaic-user-auth';
import {
DeviceAuthorizationResponseCode,
DevicePollResponseCode,
} from '@axinom/mosaic-user-auth-utils';

const DeviceSignIn: React.FC = () => {
const { requestDeviceAuthorization, pollDeviceRefreshToken, getDeviceAccessToken } =
useUserService();

const signIn = async (): Promise<void> => {
// 1. Start the flow. `deviceLabel` is optional and shown to the user on the approval screen.
const authorization = await requestDeviceAuthorization({ deviceLabel: 'Living Room TV' });
if (authorization.code !== DeviceAuthorizationResponseCode.SUCCESS) {
return; // Device Authorization is not enabled for this application, or the request failed.
}

// 2. Show `authorization.userCode` and `authorization.verificationUri` to the user
// (optionally render `authorization.verificationUriComplete` as a QR code).

// 3. Poll until the user approves on the activation page. The helper honours the poll interval
// and backs off automatically when asked to slow down.
const poll = await pollDeviceRefreshToken(authorization.deviceCode!, {
intervalSeconds: authorization.interval ?? 5,
});
if (poll.code !== DevicePollResponseCode.SUCCESS) {
return; // The request was denied or expired.
}

// 4. Store the refresh token securely on the device, then get an access token.
const refreshToken = poll.refreshToken!;
const token = await getDeviceAccessToken(refreshToken);
// `token.user?.token.accessToken` is now usable against the Mosaic end-user APIs.
};

return <button onClick={signIn}>Sign in</button>;
};

On the activation page

The activation page needs a signed-in user, because approving a device is done on that user's behalf. Sign the user in first with any of the existing flows (see Sign in with External IDPs or Sign in with AxAuth IDP) and obtain their access token with getToken.

With a signed-in user, look up the code so you can show the user what they are about to authorize, then approve or deny it.

import { useUserService } from '@axinom/mosaic-user-auth';
import {
DeviceVerificationResponseCode,
DeviceApprovalResponseCode,
} from '@axinom/mosaic-user-auth-utils';

const Activate: React.FC<{ userCode: string; accessToken: string }> = ({
userCode,
accessToken,
}) => {
const { getDeviceVerification, approveDevice, denyDevice } = useUserService();

const confirm = async (): Promise<void> => {
// Show what the code will authorize (the device label the device supplied).
const verification = await getDeviceVerification(userCode);
if (verification.code !== DeviceVerificationResponseCode.SUCCESS) {
return; // The code is unknown or has expired.
}

// Approve on behalf of the signed-in user (or call `denyDevice` to reject).
const result = await approveDevice(accessToken, userCode);
if (result.code === DeviceApprovalResponseCode.ACCESS_TOKEN_EXPIRED) {
// The access token expired: refresh it with `getToken` and retry.
}
};

return <button onClick={confirm}>Allow this device</button>;
};

The device label is supplied by the device and is display-only, meant to help the user recognize their device. The real safeguard is the explicit approval combined with the code shown on the device, so always show the user what they are approving.

Getting and renewing the access token

The device never receives an access token directly from the poll; it receives only the refresh token. Use getDeviceAccessToken to exchange the refresh token for an access token, both for the first token right after approval and for every later renewal. The refresh token is reused each time (it is not replaced), so keep it in secure device storage.

The device session lasts as long as the Native application's refresh-token lifetime. Set that lifetime higher on the device (Native) application than on a web application for the long "stay signed in" experience users expect on a TV. When the refresh token eventually expires, the device simply runs the flow again.

Handling responses

Every call returns a { code } value you should branch on:

  • Starting the flow (requestDeviceAuthorization): SUCCESS, or DEVICE_FLOW_NOT_ENABLED / APPLICATION_NOT_ACTIVE if the application is not a Native app with the flow enabled.
  • Polling (pollDeviceRefreshToken): resolves with SUCCESS (and the refreshToken), ACCESS_DENIED (denied or the account is not active), or EXPIRED (the code timed out). The helper handles the intermediate AUTHORIZATION_PENDING and SLOW_DOWN states for you, and calls the optional onPending callback on each one.
  • Looking up a code (getDeviceVerification): SUCCESS, NOT_FOUND, or EXPIRED.
  • Approving or denying (approveDevice / denyDevice): SUCCESS; NOT_FOUND, ALREADY_USED, or EXPIRED; or, when the caller is not authenticated, ACCESS_TOKEN_EXPIRED (refresh the access token with getToken and retry) versus NEEDS_LOGIN (the user must sign in again).

Reference and example

Was this page helpful?