> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liv.tv/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Device pairing, login polling, subscription validation, logout handling, and LIV Hub integration for the LCK streaming module in Unreal Engine.

<Info>
  **Module:** LCKStreaming | **Version:** 1.0 | **Platforms:** Win64, Android (Quest)
</Info>

## Overview

LCKStreaming uses a device pairing flow for authentication. The user is shown a short code in-game which they enter on a web dashboard from any device (phone, PC, tablet). The SDK polls the backend until the code is confirmed, then fetches the user profile and streaming configuration.

This approach avoids keyboard input in VR — the user pairs on a companion device.

## Device Pairing Flow

```
┌──────────┐         ┌──────────────┐         ┌─────────────┐
│  Game    │         │  LIV API     │         │  User       │
│  (SDK)   │         │  Backend     │         │  (Browser)  │
└────┬─────┘         └──────┬───────┘         └──────┬──────┘
     │  StartLogin()        │                        │
     │─────────────────────>│                        │
     │  CreateDeviceLogin   │                        │
     │─────────────────────>│                        │
     │                      │                        │
     │  OnPairingCodeReceived("ABC123")              │
     │<─────────────────────│                        │
     │                      │                        │
     │  Display code in UI  │                        │
     │                      │   User visits          │
     │                      │   dashboard.liv.tv/pair│
     │                      │<───────────────────────│
     │                      │   Enters "ABC123"      │
     │                      │<───────────────────────│
     │                      │                        │
     │  Poll (every 2.5s)   │                        │
     │─────────────────────>│                        │
     │  ...                 │                        │
     │  Poll → Authenticated│                        │
     │<─────────────────────│                        │
     │                      │                        │
     │  GetUserProfile()    │                        │
     │─────────────────────>│                        │
     │  OnAuthenticated     │                        │
     │  OnStreamingConfigReceived                    │
     │<─────────────────────│                        │
```

## Starting Login

Call `StartLogin()` to begin the pairing process. The SDK creates a device login attempt and broadcasts the pairing code.

```cpp theme={null}
ULCKStreamingSubsystem* Streaming =
    GetGameInstance()->GetSubsystem<ULCKStreamingSubsystem>();

// Bind the pairing code delegate first
Streaming->OnPairingCodeReceived.AddDynamic(
    this, &UMyAuth::OnPairingCode);
Streaming->OnAuthenticated.AddDynamic(
    this, &UMyAuth::OnAuthenticated);

// Begin login
Streaming->StartLogin();
```

## Displaying the Pairing Code

When `OnPairingCodeReceived` fires, display the code prominently in your UI alongside the URL `dashboard.liv.tv/pair`.

```cpp theme={null}
UFUNCTION()
void UMyAuth::OnPairingCode(const FString& Code)
{
    UE_LOG(LogTemp, Log, TEXT("Enter code %s at dashboard.liv.tv/pair"), *Code);

    // Update your UI widget
    if (PairingWidget)
    {
        PairingWidget->SetCodeText(Code);
        PairingWidget->SetVisibility(ESlateVisibility::Visible);
    }
}
```

You can also retrieve the current code at any time:

```cpp theme={null}
FString CurrentCode = Streaming->GetPairingCode();
```

<Tip>
  Design the pairing screen for VR readability — use large, high-contrast text. The user may need to read the code through a headset and type it on a phone.
</Tip>

## Polling Mechanism

After the pairing code is issued, the SDK automatically polls the LIV backend every **2.5 seconds** to check whether the user has entered the code. No manual polling is required.

On success, the SDK:

1. Stores the auth token internally
2. Calls `GetUserProfile()` to fetch streaming configuration
3. Broadcasts `OnAuthenticated`
4. Broadcasts `OnStreamingConfigReceived` with the target type and subscription info

<Note>
  The pairing code has an expiration time. If the code expires before the user enters it, call `StartLogin()` again to generate a new code.
</Note>

## Canceling Login

If the user wants to cancel the pairing process:

```cpp theme={null}
Streaming->CancelLogin();
```

This stops the polling timer and clears the current login attempt.

## Profile and Subscription Info

After authentication, the `OnStreamingConfigReceived` delegate provides the user's streaming target and subscription status.

```cpp theme={null}
UFUNCTION()
void UMyAuth::OnStreamingConfigReceived(
    ELCKStreamingTargetType TargetType,
    const FLCKUserSubscription& Subscription)
{
    // TargetType: None, YouTube, Twitch, Manual
    UE_LOG(LogTemp, Log, TEXT("Target: %s, Subscription active: %s"),
        *UEnum::GetValueAsString(TargetType),
        Subscription.bIsActive ? TEXT("Yes") : TEXT("No"));
}
```

### Types

```cpp theme={null}
UENUM(BlueprintType)
enum class ELCKStreamingTargetType : uint8
{
    None     UMETA(DisplayName = "None"),
    YouTube  UMETA(DisplayName = "YouTube"),
    Twitch   UMETA(DisplayName = "Twitch"),
    Manual   UMETA(DisplayName = "Manual RTMP")
};

USTRUCT(BlueprintType)
struct FLCKUserSubscription
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming")
    FString Sku;

    UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming")
    bool bIsActive = false;
};
```

### Querying State After Login

```cpp theme={null}
// Check authentication
bool bLoggedIn = Streaming->IsAuthenticated();

// Check subscription
bool bSubscribed = Streaming->HasActiveSubscription();

// Check streaming target
bool bHasTarget = Streaming->HasStreamingTarget();
ELCKStreamingTargetType Target = Streaming->GetStreamingTargetType();
FString TargetName = Streaming->GetStreamingTargetName(); // "YouTube", "Twitch", "Manual"
```

### Refreshing Configuration

If the user changes their streaming target on the dashboard, refresh the config:

```cpp theme={null}
// Visible refresh (broadcasts OnStreamingConfigReceived on completion)
Streaming->RefreshStreamingConfig(false);

// Silent refresh (suppresses error broadcasts for transient failures)
Streaming->RefreshStreamingConfig(true);
```

## Logout

Call `Logout()` to clear the stored auth token and reset the subsystem state.

```cpp theme={null}
Streaming->OnLoggedOut.AddDynamic(this, &UMyAuth::OnLoggedOut);

Streaming->Logout();
```

```cpp theme={null}
UFUNCTION()
void UMyAuth::OnLoggedOut()
{
    FString Reason = Streaming->GetLastLogoutReason();
    if (!Reason.IsEmpty())
    {
        UE_LOG(LogTemp, Log, TEXT("Logged out: %s"), *Reason);
    }

    // Return to login screen
    ShowLoginUI();
}
```

<Warning>
  `Logout()` stops any active stream before clearing credentials. Ensure your UI handles the `OnStreamStopped` delegate if a stream is active at logout time.
</Warning>

## LIV Hub Companion App (Android)

On Android/Quest, users can authenticate and manage streaming settings through the **LIV Hub** companion app instead of a web browser.

```cpp theme={null}
// Check if LIV Hub is installed
if (Streaming->IsHubInstalled())
{
    // Launch LIV Hub for authentication
    Streaming->LaunchHub();
}
else
{
    // Fall back to standard device pairing
    Streaming->StartLogin();
}
```

<Note>
  `LaunchHub()` and `IsHubInstalled()` are only functional on Android. On other platforms, `IsHubInstalled()` returns `true` (no separate hub dependency) and `LaunchHub()` is a no-op.
</Note>

## Error Handling

Authentication errors surface through the `OnStreamError` delegate and in `LogLCKStreaming` output.

Common error scenarios:

| Scenario                  | Behavior                                                    |
| ------------------------- | ----------------------------------------------------------- |
| Network unreachable       | `StartLogin()` fails, no pairing code issued                |
| Pairing code expired      | Polling stops, call `StartLogin()` again                    |
| Invalid Tracking ID       | API returns error, logged to `LogLCKStreaming`              |
| Subscription inactive     | `HasActiveSubscription()` returns `false`                   |
| Token revoked server-side | `OnLoggedOut` fires with reason via `GetLastLogoutReason()` |

## Complete Authentication Example

```cpp theme={null}
UCLASS()
class UMyAuthManager : public UActorComponent
{
    GENERATED_BODY()

public:
    virtual void BeginPlay() override
    {
        Super::BeginPlay();

        Streaming = GetWorld()->GetGameInstance()->GetSubsystem<ULCKStreamingSubsystem>();
        if (!Streaming) return;

        Streaming->OnPairingCodeReceived.AddDynamic(this, &UMyAuthManager::OnPairingCode);
        Streaming->OnAuthenticated.AddDynamic(this, &UMyAuthManager::OnAuthenticated);
        Streaming->OnStreamingConfigReceived.AddDynamic(this, &UMyAuthManager::OnConfig);
        Streaming->OnLoggedOut.AddDynamic(this, &UMyAuthManager::OnLoggedOut);
        Streaming->OnStreamError.AddDynamic(this, &UMyAuthManager::OnError);
    }

    UFUNCTION(BlueprintCallable, Category = "Streaming")
    void Login()
    {
        if (Streaming->IsAuthenticated())
        {
            UE_LOG(LogTemp, Log, TEXT("Already authenticated"));
            return;
        }
        Streaming->StartLogin();
    }

    UFUNCTION(BlueprintCallable, Category = "Streaming")
    void DoLogout()
    {
        Streaming->Logout();
    }

private:
    UFUNCTION()
    void OnPairingCode(const FString& Code)
    {
        UE_LOG(LogTemp, Log, TEXT("Pairing code: %s — enter at dashboard.liv.tv/pair"), *Code);
    }

    UFUNCTION()
    void OnAuthenticated()
    {
        UE_LOG(LogTemp, Log, TEXT("Login successful"));
    }

    UFUNCTION()
    void OnConfig(ELCKStreamingTargetType TargetType, const FLCKUserSubscription& Subscription)
    {
        UE_LOG(LogTemp, Log, TEXT("Target: %s | Sub active: %s"),
            *UEnum::GetValueAsString(TargetType),
            Subscription.bIsActive ? TEXT("Yes") : TEXT("No"));
    }

    UFUNCTION()
    void OnLoggedOut()
    {
        UE_LOG(LogTemp, Log, TEXT("Logged out: %s"), *Streaming->GetLastLogoutReason());
    }

    UFUNCTION()
    void OnError(const FString& ErrorMessage)
    {
        UE_LOG(LogTemp, Error, TEXT("Stream error: %s"), *ErrorMessage);
    }

    UPROPERTY()
    ULCKStreamingSubsystem* Streaming = nullptr;
};
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Pairing code not appearing">
    1. Verify `bEnableStreaming` is enabled in project settings
    2. Check network connectivity to `dashboard.liv.tv`
    3. Ensure the Tracking ID is configured and valid
    4. Check `LogLCKStreaming` for API error responses
  </Accordion>

  <Accordion title="Authentication succeeds but no streaming target">
    1. The user has not configured a streaming target at `dashboard.liv.tv`
    2. Call `RefreshStreamingConfig(false)` to re-fetch
    3. Verify `HasStreamingTarget()` and `HasActiveSubscription()` return values
  </Accordion>

  <Accordion title="OnLoggedOut fires unexpectedly">
    1. Check `GetLastLogoutReason()` for details
    2. The token may have been revoked server-side (e.g., 401/403 on config refresh)
    3. Network interruptions can cause auth state to reset
  </Accordion>

  <Accordion title="LIV Hub not launching on Quest">
    1. Verify LIV Hub is installed with `IsHubInstalled()`
    2. The app package is `tv.liv.controlcenter`
    3. Fall back to standard pairing if Hub is unavailable
  </Accordion>
</AccordionGroup>

## See Also

<CardGroup cols={2}>
  <Card title="Streaming Overview" icon="signal-stream" href="/unreal/streaming/overview">
    Architecture and prerequisites
  </Card>

  <Card title="Quick Start" icon="rocket" href="/unreal/streaming/quickstart">
    Get streaming in 5 minutes
  </Card>
</CardGroup>
