Step 2. Log in
Implement login so that users can log in to the app with their Apple account without signing up separately. Before you begin, complete Step 1. Set up the integration.
For Apple login, the way to get credentials and the login flow differ depending on the OS the app runs on.
| OS | How to get credentials | Behavior flow |
|---|---|---|
| iOS, macOS | Apple login Add-on | Direct Token flow |
| Android, Windows | Web login session | Authorization Code flow |
On iOS and macOS, the Add-on returns an identityToken that the Hive Axyl authentication server can validate directly, so you log in without an exchange step. On other OSs, the web login session returns only an Apple authorization code. Exchange the authorization code with Exchange external authorization codes, and then log in.
1. Get Apple credentials
Get Apple credentials in the way that matches the OS the app runs on.
1.1. iOS and macOS
On iOS and macOS, the Apple login Add-on opens the OS's Apple login window and returns the credentials of the user who completed authentication.
Prepare the nonce
nonce is a single-use random value that verifies that the Apple identityToken was issued for the current login request. Using this value prevents attacks that intercept and reuse a token issued for a different request.
Pass the Add-on the SHA256 hash of the random value as a lowercase hexadecimal string, not the random value itself. The Add-on only sends the value it receives in the Apple authentication request as is; it does not create or validate the value itself. Generate a new random value for each login.
using System;
using System.Security.Cryptography;
// Encode a cryptographically secure 32-byte random value as URL-safe base64 (padding removed).
static string CreateNonce()
{
var bytes = new byte[32];
using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(bytes);
return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
To convert it to a hash, use the Sha256Hex() helper defined in Log in with a username.
Call the login Add-on
LoginAsync
Call IAppleSignInPlugin.LoginAsync() to open the Apple login window and receive credentials.
| Field name | Type | Required | Description |
|---|---|---|---|
NonceHash | string | Required | SHA256 hash of the random value the app generated. Enter it as a lowercase hexadecimal string. |
RequestedScopes | IReadOnlyList<RequestedScope> | Required | User information to request from Apple. Specify Email and FullName. The default value is an empty list, and with an empty list you receive only the user identifier. |
If you put Unspecified in RequestedScopes or leave NonceHash empty, ArgumentException is thrown.
using Hive.Axyl.Auth.Addon.Apple;
using Hive.Axyl.Core;
using UnityEngine;
// The Add-on is registered only in iOS and macOS builds.
if (!HiveCore.TryResolve<IAppleSignInPlugin>(out var apple))
{
// Not iOS or macOS, or the Add-on is not registered → branch to the web login session
return;
}
string rawNonce = CreateNonce();
var loginResult = await apple.LoginAsync(new AppleSignInServiceLoginRequest {
NonceHash = Sha256Hex(rawNonce),
RequestedScopes = new[] { RequestedScope.Email, RequestedScope.FullName },
});
string appleProviderToken; // ProviderToken of the login request
string appleProviderUserId; // ProviderUserId of the login request
switch (loginResult)
{
case AppleSignInServiceLoginResult.Success success:
appleProviderToken = success.Data.IdentityToken;
appleProviderUserId = success.Data.UserIdentifier;
// The email and name are filled in only on the first login.
SaveProfileIfPresent(
success.Data.Email,
success.Data.UserName.GivenName,
success.Data.UserName.FamilyName);
break;
case AppleSignInServiceLoginResult.UserCanceled:
// The user closed the Apple login window → keep the login screen
return;
case AppleSignInServiceLoginResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
return;
// Safety net: unhandled results and unknown new results (UnknownOutcome)
default:
Debug.LogWarning($"Unhandled result: {loginResult.GetType().Name}");
return;
}
SaveProfileIfPresent() is code that the app implements itself so that values are saved only when they are not empty.
Response data
On success, Data of AppleSignInServiceLoginResult.Success contains the credentials that Apple returned.
| Field name | Type | Required | Description |
|---|---|---|---|
Data.UserIdentifier | string | Required | User identifier issued by Apple. Use it as ProviderUserId in the login request. |
Data.IdentityToken | string | Required | Authentication token issued by Apple. Use it as ProviderToken in the login request. |
Data.AuthorizationCode | string | Required | Apple's authorization code. It is not used in this flow. It is an empty string if Apple does not provide a value. |
Data.Email | string | Required | The user's email address. It is filled in only on the first login and is an empty string afterward. If the user chooses to hide their email, it contains a substitute address created by Apple. |
Data.UserName | AppleUserName | Required | The user's name, divided into GivenName, FamilyName, and MiddleName. They are filled in only on the first login and are all empty strings afterward. |
Data.RealUserStatus | RealUserStatus | Required | Whether Apple judges the user to be a real user. One of LikelyReal, Unknown, Unsupported, and Unspecified. |
Warning
The email and name are delivered only once, when the user logs in to this app for the first time. You cannot receive them again on later logins, so if your app uses this information, save the values at the time of the first login.
Response status
| Response case | Description | App client handling |
|---|---|---|
Success | Credentials obtained successfully | Proceed with external authentication provider login |
UserCanceled | The user closed the Apple login window | Keep the login screen |
UnknownOutcome | A new result that this SDK version does not know | Log it and handle it conservatively |
Failure | Common Failure. Cancellation by the app (Cancelled) also branches here, and the cause is stored in Failure.Problem.Code. See Common error handling. | Handle according to the common error handling criteria |
When the app needs to stop authentication first, such as when the user leaves the login screen, call CancelCurrentSession(). The pending LoginAsync() ends with Failure, and Failure.Problem.Code contains Cancelled.
1.2. OSs other than iOS and macOS
On Android and Windows, open the Apple login page with a web login session and receive an authorization code. Apple sends authentication results only to registered HTTPS addresses, so the authentication result returns to the app through the Hive Axyl relay URL.
The app receives Apple credentials in the following order.
- Create and keep a new random value, and build the
statevalue with it. - Build an Apple authorization URL that uses the Hive Axyl relay URL as
redirect_uri. - Put the app callback URL in
OpenRequest.RedirectUriand open a web login session. - Check the callback's
state, and then take out the authorization code. - Send the authorization code to external authorization code exchange to receive credentials.
Build the state
state is a value that joins a prefix, which tells the Hive Axyl relay URL where to deliver the result, with a random value that the app creates. The Hive Axyl relay URL removes the prefix and delivers only the random value to the app, so the app keeps this random value and compares it with the callback's state. This random value filters out authentication results that the app did not start, so create a new one each time you attempt login.
- Android:
{app callback URL}|{random value}. Example:{appId}://oauth-callback|{random value} - Windows:
{port}:{random value}.{port}is the port number of the local address reserved withAllocateLoopbackRedirectUriAsync()
Do not include the separator character | in the app callback URL or the random value. The app callback URL is the value determined in Relay URL and app callback URL, and the random value is created with CreateNonce(), defined in Prepare the nonce. The values that this helper creates do not contain the | character.
Build the authorization URL
Build the authorization URL by appending the following parameters to the Apple authorization endpoint https://appleid.apple.com/auth/authorize. URL-encode each value.
response_type:codeclient_id: The Service ID prepared in Apple login integrationredirect_uri: The Hive Axyl relay URLhttps://core-api.hiveaxyl.com/auth/v1/provider/callback. It must match the value you registered as the Return URL of the Service ID.response_mode:form_postscope:name email, only when you request the name and emailstate: The value created in Build the state
Do not put the PKCE code_challenge in the Apple authorization URL, and do not specify CodeVerifier in the exchange request either.
Open a web login session
Open the authorization URL you built with OpenAsync() of the web login session. Put the app callback URL, not the relay URL, in OpenRequest.RedirectUri. The app callback URL is {appId}://oauth-callback on Android and the reserved local address on Windows.
Check the callback
The Hive Axyl relay URL appends code or error from Apple's response and the state with its prefix removed as query parameters, and delivers them to the app callback URL. error contains the value that Apple sent as is.
When you receive the callback, check state first. If the callback's state differs from the random value you kept, the response did not start from this login, so stop the login. Even if state matches, stop the login if there is an error or no code. If error is user_cancelled_authorize, the user canceled Apple login; any other error means the login failed.
Exchange the external authorization code
Send the callback's code to Exchange external authorization codes to convert it into credentials to use for login. Put the following values in the exchange request.
ProviderId:Provider.SigninAppleProviderCode: The callback'scodeRedirectUri: The relay URL you put inredirect_uriof the authorization URL. This is a different value from the app callback URL you put inOpenRequest.RedirectUriCodeVerifier: Not specified
If the exchange fails, stop the login. For handling by response case, see Exchange external authorization codes.
using System;
using Hive.Axyl.Auth;
using Hive.Axyl.Auth.Addon.WebAuth;
using Hive.Axyl.Core;
if (!HiveCore.TryResolve<IExternalUserAgent>(out var webAuth))
{
return;
}
IAuthService auth = HiveCore.Resolve<IAuthService>();
const string RelayUrl = "https://core-api.hiveaxyl.com/auth/v1/provider/callback";
// Create and keep a new value each time you attempt login.
string realState = CreateNonce();
// This is an Android example. On Windows, use the reserved local address for appCallback
// and $"{port}:{realState}" for state.
string appCallback = "{appId}://oauth-callback";
string state = $"{appCallback}|{realState}";
string authorizationUrl = "https://appleid.apple.com/auth/authorize"
+ "?response_type=code"
+ "&client_id=" + Uri.EscapeDataString("{appleServiceId}")
+ "&redirect_uri=" + Uri.EscapeDataString(RelayUrl)
+ "&response_mode=form_post"
+ "&scope=" + Uri.EscapeDataString("name email")
+ "&state=" + Uri.EscapeDataString(state);
var sessionResult = await webAuth.OpenAsync(new OpenRequest {
Url = authorizationUrl,
RedirectUri = appCallback, // The app callback URL, not the relay URL
});
if (sessionResult is not ExternalUserAgentServiceOpenResult.Success session)
{
// For UserCanceled and Failure handling, see [Web login session](web-auth-session.md)
return;
}
var callback = session.Data.Parameters;
// Check state first.
if (!callback.TryGetValue("state", out var returnedState) || returnedState != realState)
{
// Not a response started by this login → stop the login
return;
}
if (callback.TryGetValue("error", out var appleError))
{
// If "user_cancelled_authorize", the user canceled → keep the login screen
// Any other value means login failed → stop the login
return;
}
if (!callback.TryGetValue("code", out var appleCode))
{
// No authorization code → stop the login
return;
}
// Exchange the authorization code from the callback for credentials to use for login.
var exchange = await auth.ExchangeProviderTokenAsync(new ProviderTokenRequest {
ProviderId = Provider.SigninApple,
ProviderCode = appleCode,
RedirectUri = RelayUrl, // Same value as redirect_uri of the authorization URL
});
if (exchange is not AuthExchangeProviderTokenResult.Success exchanged)
{
// Exchange failed → stop the login
return;
}
string appleProviderToken = exchanged.Data.ProviderToken; // ProviderToken of the login request
string appleProviderUserId = exchanged.Data.ProviderUserId; // ProviderUserId of the login request
2. Log in with an external authentication provider
Call Log in with an external authentication provider with the appleProviderUserId and appleProviderToken obtained in the previous step. Specify Provider.SigninApple for ProviderId.
using Hive.Axyl.Auth;
using Hive.Axyl.Core;
IAuthService auth = HiveCore.Resolve<IAuthService>();
var (codeVerifier, codeChallenge) = CreatePkce();
var result = await auth.LoginProviderAsync(new ProviderLoginRequest {
ProviderId = Provider.SigninApple,
ProviderUserId = appleProviderUserId,
ProviderToken = appleProviderToken,
DeviceKey = deviceKey,
ClientId = "{clientId}",
CodeChallenge = codeChallenge,
CodeChallengeMethod = CodeChallengeMethod.S256,
});
if (result is AuthLoginProviderResult.Success success)
{
// Login succeeded → issue tokens and activate the session.
await StartSessionAsync(success.Data.AuthorizationCode, codeVerifier, success.Data.PlayerId);
}
// For other response cases and all call parameters, see [Log in with an external authentication provider](provider-login.md)
Note
The PKCE values generated here are used when exchanging the authorization code issued by the Hive Axyl authentication server for tokens. Apple does not require them, so do not put them in the Apple authorization URL.
CreatePkce() is a helper defined in Create a guest account. For the definition of StartSessionAsync() and the session activation procedure, see Issue tokens and activate the session.