---
title: "Sign in players"
description: "Sign in with a configured provider, use the player's Crossplay identity, and sign out."
seo:
  noindex: true
---

{/* Copyright (c) 2026 Betide Studio. All Rights Reserved. */}

## Sign in

Configure your provider in **Project Settings → Betide → Crossplay**: [EOS setup](/integrations/eos/setup) · [Cognito setup](/integrations/aws/cognito).

For button-driven login, disable **Auto Login On Start**. For EOS, also set **Auto Login Type** to **None**.

**Blueprint**

<BlueprintViewer src="/blueprints/sign-in-account-portal.txt" title="Sign in with Account Portal" />

**C++**

Game module dependencies: `UCIKCore`, `BetideCore`.

```cpp
#include "Engine/GameInstance.h"
#include "UCIKIdentitySubsystem.h"

DEFINE_LOG_CATEGORY_STATIC(LogGameIdentity, Log, All);

void SignInPlayer(UGameInstance *GameInstance)
{
    if (!IsValid(GameInstance))
    {
        return;
    }

    UUCIKIdentitySubsystem *Identity = GameInstance->GetSubsystem<UUCIKIdentitySubsystem>();
    if (!Identity || Identity->IsLoginInProgress())
    {
        return;
    }

    FUCIK_LoginRequest Request;
    Request.LocalUserIndex = 0;
    Request.MethodId = TEXT("Betide.EOS.Auth.AccountPortal");
    Request.AccountPolicy = EUCIK_AccountPolicy::SignInOrCreate;

    Identity->Login(Request,
                    FUCIKLoginUpdateCallback::CreateWeakLambda(
                        GameInstance, [](const FUCIK_LoginUpdate &Update) {
                            if (!Update.Operation.IsTerminal())
                            {
                                return;
                            }

                            if (Update.Operation.State == EUCIK_LoginOperationState::Succeeded)
                            {
                                UE_LOG(LogGameIdentity, Log, TEXT("User ID: %s"),
                                       *Update.Identity.PrimaryHandle.Id);
                                UE_LOG(LogGameIdentity, Log, TEXT("User display name: %s"),
                                       *Update.Identity.DisplayName);
                                return;
                            }

                            UE_LOG(LogGameIdentity, Warning, TEXT("Sign-in failed: %s"),
                                   *Update.Error.GetLogString());
                        }));
}
```

When updating a widget, bind the callback weakly to that widget. Login can complete synchronously.

Keep the full **Player** handle for other Crossplay calls. Prevent overlapping login attempts.

## Sign out

Set **Scope** to **Authority Session**. An empty **Authority** uses the current identity. For EOS account switching, log out first.

**Blueprint**

<BlueprintViewer title="Sign out a player" src="/blueprints/crossplay-signout.txt" />

**C++**

Uses the includes and log category from the sign-in example.

```cpp
void SignOutPlayer(UGameInstance *GameInstance)
{
    if (!IsValid(GameInstance))
    {
        return;
    }

    UUCIKIdentitySubsystem *Identity = GameInstance->GetSubsystem<UUCIKIdentitySubsystem>();
    if (!Identity || !Identity->GetIdentity().PrimaryHandle.IsValid())
    {
        return;
    }

    Identity->Logout({}, 0, EUCIK_LogoutScope::AuthoritySession,
                     FUCIKLogoutCallback::CreateWeakLambda(
                         GameInstance, [](const TBetideResult<FUCIK_LogoutResult> &Result) {
                             if (!Result.bOk)
                             {
                                 UE_LOG(LogGameIdentity, Warning, TEXT("Sign-out failed: %s"),
                                        *Result.Error.GetLogString());
                                 return;
                             }

                             UE_LOG(LogGameIdentity, Log, TEXT("Signed out. Completed scope: %d"),
                                    Result.GetValue().CompletedScope);
                         }));
}
```

Logout clears UCIK's cached identity. **Completed Scope** reports the provider's completed actions; launcher/browser sign-out is not guaranteed.

## More options

<Accordion>
  <AccordionItem title="Automatic method selection">

**Auto** uses the first eligible **Auto Login Route** for the platform, otherwise the available method with the highest priority. Configure routes under **Show all** in Crossplay settings. A failed login does not retry another route.

This method also works with button-driven login. In C++, set `Request.MethodId = TEXT("Auto")`; an empty method is invalid.

  </AccordionItem>
  <AccordionItem title="Startup login and cached identity">

UCIK caches one identity per Game Instance across map changes. **Local User Index** does not create separate split-screen identity caches.

Leave **Auto Login On Start** enabled and call **Start Observing Login** when your menu opens. Do not also start login from the menu. This observer binds first, then reads the latest update. **Stop Observing Login** removes its binding without cancelling login; End Play also removes it.

**Blueprint**

<BlueprintViewer title="Observe startup login" src="/blueprints/crossplay-startup-login-observer.txt" />

Actor variables: `ObservingLogin` (**Boolean**, false) and `LastObservedUpdate` (**Crossplay Login Update**).

**C++**

`LoginObserver.h`

```cpp
// Copyright (c) 2026 Betide Studio. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Identity/UCIKIdentityTypes.h"
#include "LoginObserver.generated.h"

class UUCIKIdentitySubsystem;

UCLASS()
class ALoginObserver : public AActor
{
    GENERATED_BODY()
  public:
    UFUNCTION(BlueprintCallable) void StartObservingLogin();
    UFUNCTION(BlueprintCallable) void StopObservingLogin();
    UPROPERTY(BlueprintReadOnly) FUCIK_LoginUpdate LastObservedUpdate;

  protected:
    void EndPlay(const EEndPlayReason::Type Reason) override;

  private:
    TWeakObjectPtr<UUCIKIdentitySubsystem> Identity;
    FDelegateHandle LoginBinding;
    void HandleLoginUpdate(const FUCIK_LoginUpdate &Update);
};
```

`LoginObserver.cpp`

```cpp
// Copyright (c) 2026 Betide Studio. All Rights Reserved.
#include "LoginObserver.h"
#include "Engine/GameInstance.h"
#include "UCIKIdentitySubsystem.h"
#include "UCIKCore.h"
#include "UObject/Class.h"

void ALoginObserver::StartObservingLogin()
{
    if (LoginBinding.IsValid())
        return;
    UGameInstance *Game = GetGameInstance();
    Identity = Game ? Game->GetSubsystem<UUCIKIdentitySubsystem>() : nullptr;
    if (!Identity.IsValid())
    {
        UE_LOG(LogUCIK, Warning, TEXT("Identity subsystem is unavailable"));
        return;
    }
    LoginBinding =
        Identity->OnLoginStateChangedNative.AddUObject(this, &ThisClass::HandleLoginUpdate);
    HandleLoginUpdate(Identity->GetLastLoginUpdate());
}

void ALoginObserver::HandleLoginUpdate(const FUCIK_LoginUpdate &Update)
{
    LastObservedUpdate = Update;
    if (!Update.Operation.IsValid())
    {
        UE_LOG(LogUCIK, Log, TEXT("No recorded login operation; signed-out/reset state"));
        return;
    }
    UE_LOG(LogUCIK, Log, TEXT("%s"), *UEnum::GetValueAsString(Update.Operation.State));
    if (Update.Operation.State == EUCIK_LoginOperationState::Succeeded)
    {
        const FUCIK_PlayerHandle Current =
            Identity.IsValid() ? Identity->GetIdentityHandle(Update.Identity.PrimaryAuthority)
                               : FUCIK_PlayerHandle{};
        if (!Current.Id.IsEmpty() && Current.Id == Update.Identity.PrimaryHandle.Id)
            UE_LOG(LogUCIK, Log, TEXT("%s"), *Update.Identity.DisplayName);
        else
            UE_LOG(LogUCIK, Log, TEXT("That login no longer matches the cached identity"));
        return;
    }
    if (Update.Error.IsError())
    {
        UE_LOG(LogUCIK, Warning, TEXT("%s"), *Update.Error.GetLogString());
        return;
    }
    if (Update.Operation.State == EUCIK_LoginOperationState::NeedsChallengeResponse ||
        Update.Operation.State == EUCIK_LoginOperationState::NeedsUserInterface)
        UE_LOG(LogUCIK, Log, TEXT("%s"), *Update.Challenge.Prompt.ToString());
}

void ALoginObserver::StopObservingLogin()
{
    if (Identity.IsValid())
        Identity->OnLoginStateChangedNative.Remove(LoginBinding);
    LoginBinding.Reset();
    Identity.Reset();
}

void ALoginObserver::EndPlay(const EEndPlayReason::Type Reason)
{
    StopObservingLogin();
    Super::EndPlay(Reason);
}
```

  </AccordionItem>
  <AccordionItem title="Login challenges">

This example signs in with [Cognito SRP](/integrations/aws/cognito#setup) and answers its MFA challenge. Configure the User Pool first and disable **Auto Login On Start** for this button-driven flow.

**Blueprint**

<BlueprintViewer title="Cognito login and challenges" src="/blueprints/crossplay-aws-cognito-login.txt" />

**C++**

Game module dependencies: `UCIKCore`, `BetideCore`, `Engine`. Use this Game Instance class, or merge its members into your own.

```cpp
// CognitoLoginGameInstance.h
#pragma once

#include "Engine/GameInstance.h"
#include "UCIKIdentitySubsystem.h"
#include "CognitoLoginGameInstance.generated.h"

UCLASS()
class UCognitoLoginGameInstance : public UGameInstance
{
    GENERATED_BODY()

  public:
    UFUNCTION(BlueprintCallable)
    void SignInCognito(const FString &Username, const FString &Password);

    UFUNCTION(BlueprintCallable)
    void AnswerCognitoChallenge(const FString &Answer);

    UFUNCTION(BlueprintCallable)
    void CancelCognitoChallenge();

    UPROPERTY(BlueprintReadOnly)
    FUCIK_LoginUpdate CognitoLoginUpdate;

    UPROPERTY(BlueprintReadOnly)
    bool CognitoChallengePending = false;
};

// CognitoLoginGameInstance.cpp
#include "CognitoLoginGameInstance.h"
#include "UCIKCore.h"

void UCognitoLoginGameInstance::SignInCognito(const FString &Username, const FString &Password)
{
    UUCIKIdentitySubsystem *Identity = GetSubsystem<UUCIKIdentitySubsystem>();
    if (!Identity || Identity->IsLoginInProgress())
    {
        UE_LOG(LogUCIK, Warning, TEXT("Identity unavailable or a login is already in progress"));
        return;
    }

    FUCIK_LoginRequest Request;
    Request.LocalUserIndex = 0;
    Request.MethodId = TEXT("Betide.Cognito.UserPool.SRP");
    Request.Fields.FindOrAdd(TEXT("Username")).StringValue = Username;
    Request.Fields.FindOrAdd(TEXT("Password")).StringValue = Password;
    Identity->Login(
        Request,
        FUCIKLoginUpdateCallback::CreateWeakLambda(this, [this](const FUCIK_LoginUpdate &Update) {
            CognitoLoginUpdate = Update;
            CognitoChallengePending =
                Update.Operation.State == EUCIK_LoginOperationState::NeedsChallengeResponse;
            if (CognitoChallengePending)
            {
                UE_LOG(LogUCIK, Log, TEXT("%s"), *Update.Challenge.Prompt.ToString());
            }
            else if (Update.Operation.IsTerminal())
            {
                if (Update.Operation.State == EUCIK_LoginOperationState::Succeeded)
                {
                    UE_LOG(LogUCIK, Log, TEXT("Signed in to Cognito"));
                }
                else
                {
                    UE_LOG(LogUCIK, Warning, TEXT("%s"), *Update.Error.Message);
                }
            }
        }));
}

void UCognitoLoginGameInstance::AnswerCognitoChallenge(const FString &Answer)
{
    if (!CognitoChallengePending)
    {
        UE_LOG(LogUCIK, Warning, TEXT("No Cognito challenge is waiting for an answer"));
        return;
    }
    UUCIKIdentitySubsystem *Identity = GetSubsystem<UUCIKIdentitySubsystem>();
    if (!Identity)
    {
        return;
    }
    TMap<FName, FUCIK_LoginFieldValue> Responses;
    Responses.FindOrAdd(CognitoLoginUpdate.Challenge.ResponseFields[0].FieldId).StringValue =
        Answer;
    CognitoChallengePending = false;
    Identity->ContinueLogin(CognitoLoginUpdate.Operation, Responses);
}

void UCognitoLoginGameInstance::CancelCognitoChallenge()
{
    if (!CognitoChallengePending)
    {
        UE_LOG(LogUCIK, Warning, TEXT("No Cognito challenge is waiting for an answer"));
        return;
    }
    if (UUCIKIdentitySubsystem *Identity = GetSubsystem<UUCIKIdentitySubsystem>())
    {
        CognitoChallengePending = false;
        Identity->CancelLogin(CognitoLoginUpdate.Operation);
    }
}
```

Keep this flow in your Game Instance. Required variables: `CognitoLoginUpdate` (**Crossplay Login Update**) and `CognitoChallengePending` (**Boolean**, false). Route login requests through this flow so its state listener receives only the active attempt.

Show the challenge prompt in your UI, then pass the player's answer to **Answer Cognito Challenge**. **Cancel Cognito Challenge** cancels while the prompt is waiting. Neither event starts another login. Keep passwords and answers out of logs.

This Cognito flow uses one string response field. Other methods can request several fields or a different type; use their **Response Fields** descriptors. For browser redirects, see [Cognito managed login](/integrations/aws/cognito#managed-browser-login).

  </AccordionItem>
  <AccordionItem title="Runtime methods and credentials">

**Get Crossplay Login Methods** (`GetLoginMethods` in C++) returns method descriptors. Supply the platform and filter out unconfigured/unavailable methods. Use **Runtime Login Request** from the node's context menu to supply the chosen **Method Id** and **Fields**; C++ uses `FUCIK_LoginRequest`.

UCIK normally acquires credentials. Enable **Has Credential Override** only when supplying a credential matching the method's source, kind, and audience. Secret inputs must be runtime values, not literal pin defaults.

  </AccordionItem>
  <AccordionItem title="Troubleshooting">

- **Method unavailable:** check provider configuration and target-platform support.
- **Account Portal completes without opening:** EOS reuses an existing login. Log out to switch accounts.
- **Provider error:** use **Get Error Message (Crossplay Error)** and the error's native code for diagnostics; C++ exposes `Error.GetLogString()`. Use **Get Error Text (Crossplay Error)** for player-facing UI.

  </AccordionItem>
</Accordion>

Next: [lobbies](/crossplay/lobbies) · [sessions](/crossplay/sessions).
