Limited Login Data Refresh
Updated: Aug 27, 2026
Copy for LLM
Open beta
These Facebook Login features are part of an open beta. If you encounter any issues, please use our feedback form.
Limited Login delivers a person’s profile once, as a snapshot taken at login. Limited Login Data Refresh keeps that snapshot current without sending them back through a full login flow.
Because Limited Login does not use a long-lived access token, the
Profile and AuthenticationToken issued at login grow stale over time. Data Refresh removes that friction: the SDK refreshes the active session as your app returns to the foreground, so Profile.current and AuthenticationToken.current stay fresh, and you can trigger a refresh on demand with a single call.You keep logging people in exactly as you do today, with
LoginConfiguration and .limited tracking. The refresh capability layers on top of an existing Limited Login session and operates only on Limited Login profiles.Requirements
| Requirement | Notes |
|---|---|
Facebook SDK for iOS | 18.1.0 or later — the first release containing refreshLimitedLogin. Available through Swift Package Manager (tag v18.1.0) and CocoaPods. |
iOS version | 13.0 or later for the silent refresh paths. On iOS 12 and earlier those paths return unsupportedPlatform, leaving only the explicit path. |
Keychain entitlement | A keychain-access-groups entitlement, plus a real application-identifier and a configured DEVELOPMENT_TEAM. The silent path stores a device-bound key in the keychain; without keychain access the SDK cannot create that key and refresh falls back to a path that presents UI. |
Add the entitlement to your app’s
.entitlements file. $(AppIdentifierPrefix) expands to your team’s keychain prefix at build time:<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)*</string>
</array>
How it works
Device-key binding
At Limited Login time the SDK generates a P-256 key pair, stores the private key in the keychain, and sends the public key’s JWK Thumbprint to the server as a
dpop_jkt value. The server stamps that thumbprint into the issued ID token as a cnf.jkt (“confirmation key thumbprint”) claim, binding the token to a key only your app’s keychain holds.The direct refresh path later proves possession of that key over a DPoP-bound HTTPS POST, which is what lets the SDK refresh with no user-visible UI. If the keychain is unavailable at login, the SDK omits
dpop_jkt, the resulting token carries no cnf.jkt, and the direct path cannot use it.The three refresh paths
| Path | Behavior |
|---|---|
Direct ( .directOnly) | A truly silent, device-key-bound HTTPS POST. No UI of any kind. Requires the current ID token to carry a cnf.jkt claim; otherwise it returns notDPoPBound. |
Silent ( .silentOnly) | A prompt=none OpenID Connect flow through ASWebAuthenticationSession. Apple always shows its own system consent sheet for a web authentication session, so this path is not invisible — but no Facebook login UI appears. Silent re-sends dpop_jkt, so it can mint a freshly bound token and recover a session whose token was never bound. |
Explicit ( .explicitOnly) | The standard Limited Login dialog. The person re-authenticates through the normal login flow. |
Automatic foreground refresh
There is nothing to opt into. When a Limited Login session is active, the SDK observes the app entering the foreground and attempts a direct refresh.
Settings.shared.limitedLoginAutoRefreshInterval throttles this (default 24 hours), so repeated foregrounds inside the window do not trigger repeated network calls.On success the SDK updates
Profile.current and AuthenticationToken.current and posts a ProfileDidChange notification. A token with no device-key binding is a no-op for that foreground — the next interactive login mints a bound token and unblocks subsequent foregrounds.The automatic cascade
The default policy,
.automatic, tries each path in order of increasing friction: direct → silent → explicit.- If direct fails for any reason other than
featureDisabled— includingnotDPoPBound— the SDK falls back to silent. Silent re-binds the token, which is what recovers anotDPoPBoundsession. - If silent then returns
loginRequiredorconsentRequired, the SDK falls back to explicit. - Transient failures such as
networkErrorandtimeoutnever escalate to explicit, so a connectivity blip does not present a login dialog. featureDisablednever cascades. If Data Refresh is unavailable, no path can succeed, so the SDK surfaces the error instead of presenting UI.
Set up
- Add the SDK. With Swift Package Manager, add
https://github.com/facebook/facebook-ios-sdkand theFacebookLoginproduct. With CocoaPods, addpod 'FBSDKLoginKit'and runpod install. Use 18.1.0 or later. - Add the keychain entitlement. Add a
keychain-access-groupsentry to your app’s.entitlementsfile, as shown in Requirements, and confirm the target has a validDEVELOPMENT_TEAM. This is what enables silent, device-bound refresh. - Confirm Limited Login works first. Your app should already log people in with
LoginConfiguration(permissions:tracking:nonce:)using.limitedtracking, andProfile.currentshould be populated afterward. - Nothing to opt into for automatic refresh. Once Limited Login works and the entitlement is in place, foreground refresh is automatic. Use the on-demand API only when you want explicit control.
Refresh on demand
Call
refreshLimitedLogin on a LoginManager instance. Every parameter except the completion handler has a default.public func refreshLimitedLogin(
from viewController: UIViewController? = nil,
fallbackPolicy: RefreshFallbackPolicy = .automatic,
completion: @escaping (Result<LimitedLoginRefreshResult, LimitedLoginRefreshError>) -> Void
)
Pass
nil for viewController to use the topmost view controller. A view controller is only needed if the cascade reaches the explicit path, which presents UI.import FBSDKLoginKit
LoginManager().refreshLimitedLogin(from: self) { result in
switch result {
case let .success(refresh):
// refresh.profile, refresh.authenticationToken, and the
// Profile.current / AuthenticationToken.current singletons are all current.
print("Refreshed via .\(refresh.path.rawValue)")
case let .failure(error):
print("Refresh failed: \(error.localizedDescription)")
}
}
The Objective-C variant delivers the refreshed
FBSDKProfile and an NSError directly. Path attribution is available in Swift only.Choose a fallback policy
RefreshFallbackPolicy | Behavior |
|---|---|
.automatic(default, recommended) | Cascade: direct, then silent on any failure other than featureDisabled (including notDPoPBound), then explicit if silent returns loginRequired or consentRequired. |
.directOnly | Only the device-key-bound HTTPS POST. No UI at all. Returns notDPoPBound if the current token has no cnf.jkt claim. Use when no user-visible UI is acceptable. |
.silentOnly | Only the prompt=none flow. Apple’s system consent sheet may appear; no Facebook UI. Re-binds the token if needed. |
.explicitOnly | Only the standard Limited Login dialog — guarantees the person re-authenticates. |
Read the result
A successful refresh yields a
LimitedLoginRefreshResult:public struct LimitedLoginRefreshResult {
public let profile: Profile
public let authenticationToken: AuthenticationToken
public let path: RefreshPath
}
public enum RefreshPath: String {
case direct // the device-key-bound POST
case silent // prompt=none
case explicit // the standard Limited Login dialog
}
For the single-path policies,
path always matches the policy. For .automatic it identifies whichever path succeeded — useful for tracking how often you fall back to a path that presents UI.Check how fresh the data is
Profile.refreshDate records when the current profile was last fetched. The SDK sets it on every Profile construction, so it covers initial login, refresh, and cold start from a persisted session — a reliable way to decide whether to force an on-demand refresh.if let refreshDate = Profile.current?.refreshDate,
Date().timeIntervalSince(refreshDate) > 3600 {
LoginManager().refreshLimitedLogin { _ in }
}
Observe changes
On success the SDK updates
Profile.current and AuthenticationToken.current and posts ProfileDidChange. For Limited Login the authentication token changes whenever the profile does, so a single observer on that notification keeps your UI in sync with both.Configure the refresh interval
Settings.shared.limitedLoginAutoRefreshInterval is the minimum interval, in seconds, between automatic foreground refreshes. The default is 86400 (24 hours).import FBSDKCoreKit // Refresh at most once every 6 hours. Settings.shared.limitedLoginAutoRefreshInterval = 6 * 60 * 60
This throttles only the automatic foreground refresh. On-demand calls are not gated by this interval, though a separate client-side rate limiter can still return
rateLimited.Handle errors
Failures arrive as
LimitedLoginRefreshError, which conforms to LocalizedError — use error.localizedDescription for an actionable message — and to CustomNSError, with the NSError domain com.facebook.sdk.login.refresh.| Case | Meaning | Recovery |
|---|---|---|
noCurrentToken | AuthenticationToken.current is nil — there is no session to refresh, because the person never logged in or has logged out. | Start a fresh Limited Login. |
notLimitedLogin | The current session is a classic Facebook Login session. This API refreshes Limited Login only. | Use the mechanism appropriate to classic Facebook Login. |
loginRequired | The Facebook session backing the token expired, or the session predates a required Limited Login disclosure update and needs a one-time re-login. Maps to the OIDC login_required error. | Send the person through a full login, or use .automatic, which falls back to explicit. |
consentRequired | Permissions were revoked, or new permissions are needed. Maps to the OIDC consent_required error. | Re-request consent through a full login. .automatic handles this. |
userMismatch | The refreshed token’s subject does not match the current user. The SDK refused to swap it in. | Log the person out and require a fresh login. |
networkError | Transport-level failure — DNS, connection reset, or TLS. | Retry once connectivity is restored. |
timeout | The request did not complete within the SDK timeout of 30 seconds. | Retry. Repeated timeouts suggest a backend or network issue. |
rateLimited | The client-side rate limiter is throttling attempts. | Wait before retrying. error.localizedDescription reports how long. |
invalidResponse | A 2xx response whose body could not be parsed — missing ID token, malformed JSON, or claims that failed OIDC validation. | Not retryable client-side; indicates a server or protocol issue. |
cancelled | The person cancelled an explicit login dialog. | No action. The prior session is left intact. |
featureDisabled | Data Refresh is unavailable. | Not recoverable client-side; leave the current session as it is. |
unsupportedPlatform | The device runs iOS earlier than 13.0; the silent paths need iOS 13 APIs. | Use .explicitOnly on iOS 12, or require iOS 13 or later. |
notDPoPBound | The current token has no cnf.jkt claim, so the direct path cannot use it. | Complete a fresh Limited Login to mint a bound token. Under .automatic, silent recovers this automatically. |
dpopKeyGenerationFailed | The SDK could not generate or load the device key from the keychain — most often a missing keychain-access-groups entitlement. | Add the entitlement and a valid DEVELOPMENT_TEAM. |
unknown | An error the SDK could not classify. | Retry and inspect logs. |
Unity
The Facebook SDK for Unity exposes the same capability as
FB.Mobile.RefreshLimitedLogin, starting with Facebook SDK for Unity 18.1.0. The Unity SDK is a thin bridge over the native iOS SDK, so the underlying behavior is identical to everything described above.This API is iOS-only. It is a no-op on Android and in the Unity Editor, so it is safe to compile cross-platform.
Requirements
| Requirement | Notes |
|---|---|
Facebook SDK for Unity | 18.1.0 or later |
Underlying native SDK | Facebook SDK for iOS ~> 18.1.0, resolved automatically by the External Dependency Manager for Unity at build time. You never hand-download the native SDK. |
iOS version | 13.0 or later for the silent paths |
Keychain entitlement | Enable Keychain Sharing on the generated Xcode target, with a valid development team |
Set up
- Import the SDK. Install Facebook SDK for Unity 18.1.0 or later (release notes), then let the External Dependency Manager resolve native dependencies.
- Configure Facebook login as usual. Set your app ID and client token in Facebook Settings, call
FB.Init(), and confirm a normal Limited Login works first. - Enable Keychain Sharing on the generated iOS target, with a valid development team. This is what enables silent, device-bound refresh.
- Optional — enable fast app switch for the initial login. By default the initial Limited Login runs through a web flow. To route it through a native app switch to the Facebook app, call
FB.Mobile.SetFastAppSwitchEnabled(true)afterFB.Init()and before the login call. This is iOS-only, and it affects the initial login, notRefreshLimitedLogin. The app switch happens only when the Facebook app is installed and the feature is enabled for your app; otherwise login falls back to the web flow.
Refresh on demand
FB.Mobile.RefreshLimitedLogin(
FacebookDelegate<ILoginResult> callback = null,
RefreshFallbackPolicy fallbackPolicy = RefreshFallbackPolicy.Automatic);
The result arrives through the same
FacebookDelegate<ILoginResult> callback you already use for login. On success, read the refreshed profile with FB.Mobile.CurrentProfile().#if UNITY_IOS
FB.Mobile.RefreshLimitedLogin(OnRefresh, RefreshFallbackPolicy.Automatic);
#endif
void OnRefresh(ILoginResult result)
{
if (!string.IsNullOrEmpty(result.Error)) { Debug.LogError(result.Error); return; }
if (result.Cancelled) { return; }
var profile = FB.Mobile.CurrentProfile();
}
RefreshFallbackPolicy mirrors the native policy: Automatic (default), DirectOnly, SilentOnly, and ExplicitOnly.Unity surfaces failures as a single
result.Error string rather than the native typed error, so log it verbatim during development — it mirrors the native diagnostics. result.Cancelled means the person cancelled a step that presented UI, and the prior session is left intact. Under Automatic, most failure modes resolve themselves by escalating to the next path.Troubleshooting
Direct refresh always returns notDPoPBound
The direct path needs the current ID token to carry a
cnf.jkt claim. If it does not, the token was never bound to a device key. Common causes:- Missing keychain entitlement. If
keychain-access-groupsis absent or the team identifier is misconfigured, key generation fails at login, no thumbprint is sent, and the issued token lacks the claim. Add the entitlement, then log in again. - Token issued before binding. A session created before the device key was bound does not carry the claim. Log in again.
- Device key wiped. Reinstalling the app clears the keychain key, so it no longer matches the token’s claim. Log in again.
Using
.automatic hides this for most apps: the silent path re-binds the token, so the session recovers with no special handling.dpopKeyGenerationFailed
The SDK could not generate or load the device key from the keychain, almost always because of a missing or invalid keychain entitlement. Confirm your app has a
keychain-access-groups entry, a valid application-identifier, and a configured DEVELOPMENT_TEAM. Unsigned simulator builds and development builds with no signing configured commonly hit this.Inspect logs
Attach the device and inspect the system console — Console.app or
log stream — filtered to the subsystem com.facebook.sdk. On Unity, this is where the native diagnostics behind a result.Error string appear.FAQ
Do I need to call refresh myself?
No. Foreground refresh is automatic and always on for active Limited Login sessions. The on-demand API is for explicit control — refreshing before a sensitive operation, for example, or letting someone trigger a refresh.
Does refresh show UI?
It depends on the path. Direct shows no UI at all, and it is the path automatic foreground refresh uses. Silent shows Apple’s system consent sheet for the web authentication session, but no Facebook login UI. Explicit shows the standard Limited Login dialog. With
.automatic, the SDK escalates to a path that presents UI only when the silent paths cannot recover the session.What about iOS 12?
The silent paths require iOS 13 or later and return
unsupportedPlatform on older systems. On iOS 12, use .explicitOnly.How do I know which path refreshed the session?
Read
path on the LimitedLoginRefreshResult. It is .direct, .silent, or .explicit. Swift only.How do I know how stale the current data is?
Read
Profile.current?.refreshDate. See Check how fresh the data is.What happens to
Profile.current after a refresh?
The SDK updates Profile.current and AuthenticationToken.current and posts ProfileDidChange. Observe that notification to keep your UI in sync.What happens on Android or in the Unity Editor?
FB.Mobile.RefreshLimitedLogin is a no-op. Data Refresh is an iOS-only capability.