yandex_login_sdk 1.0.1 copy "yandex_login_sdk: ^1.0.1" to clipboard
yandex_login_sdk: ^1.0.1 copied to clipboard

Native Yandex LoginSDK wrapper for Flutter — SSO via installed Yandex apps with browser fallback (iOS + Android).

yandex_login_sdk #

pub package CI Coverage Status License: BSD-3-Clause

A Flutter plugin for Yandex sign-in on Android, iOS and Web. On mobile it wraps the official Yandex LoginSDK — native single sign-on through installed Yandex apps (Browser, Mail, Старт, …) with automatic fallback to a Chrome Custom Tab, WebView, or ASWebAuthenticationSession. On web it runs the OAuth 2.0 Authorization Code + PKCE flow in a popup.

  • ✅ Native SSO via installed Yandex apps (Android / iOS)
  • ✅ Automatic browser fallback when no Yandex app is present
  • Web support — code + PKCE popup flow, token never appears in a URL
  • ✅ Optional webOnly strategy to skip the installed apps entirely
  • ✅ Cancellation surfaced as a typed exception
  • ✅ Returns the OAuth access_token (and JWT on iOS, expires_in on Android/Web)
  • ✅ Fetch the user profile (getUserInfo) and a cross-platform JWT (getJwt) — pure Dart
  • signOut() to clear local sign-in state
  • 100% Dart test coverage, every commit verified by CI

Status: v1.0.1. Android tested in production; iOS code complete but not yet field-tested by the maintainer (no Apple Developer account at the time of release). Reports/PRs welcome.

Setup #

1. Register an OAuth app in Yandex #

Create or open your app at oauth.yandex.ru, enable the mobile platform, and provide both bundle identifiers:

  • iOS Bundle ID
  • Android Package name

You'll get a client_id (32-char hex string) — used in every step below.

2. Add the dependency #

dependencies:
  yandex_login_sdk: ^1.0.0

3. Android setup #

android/app/build.gradle.kts — add manifest placeholders (required for the native SDK to initialize; the clientId you pass to signIn overrides the placeholder value at runtime):

android {
    defaultConfig {
        manifestPlaceholders["YANDEX_CLIENT_ID"] = "<your_client_id>"
        manifestPlaceholders["YANDEX_OAUTH_HOST"] = "oauth.yandex.ru"
    }
}

android/app/src/main/kotlin/.../MainActivity.kt — switch to FlutterFragmentActivity (required for ActivityResultLauncher):

import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()

That's it on Android — the plugin's manifest contributes the package visibility queries for known Yandex apps.

4. iOS setup #

ios/Runner/Info.plist — add the URL scheme returned by the SDK and declare the schemes it queries:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key><string>Editor</string>
        <key>CFBundleURLName</key><string>YandexLoginSDK</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>yx0123456789abcdef0123456789abcdef</string>
            <!-- "yx" + your client_id, no angle brackets -->
        </array>
    </dict>
</array>

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>primaryyandexloginsdk</string>
    <string>secondaryyandexloginsdk</string>
</array>

ios/Runner/SceneDelegate.swift — forward URL callbacks (only required for projects using the modern scene-based lifecycle, which is the default for Flutter 3+):

import Flutter
import UIKit
import yandex_login_sdk

class SceneDelegate: FlutterSceneDelegate {
  override func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    var handled = false
    for ctx in URLContexts {
      if YandexLoginSdkPlugin.handle(openURL: ctx.url) { handled = true }
    }
    if !handled {
      super.scene(scene, openURLContexts: URLContexts)
    }
  }
}

AppDelegate.swift needs no changes — the plugin registers itself as a FlutterApplicationLifeCycleDelegate and intercepts AppDelegate callbacks automatically (used as a backup for non-scene-based apps).

Minimum iOS deployment target: 13.0.

5. Web setup #

The web flow is plain OAuth (Authorization Code + PKCE) — no Yandex JS SDK, no client secret.

  1. At oauth.yandex.ru enable the Web services platform for your app and register the Redirect URI: https://your.app/yandex_auth_callback.html (plus http://localhost:5000/yandex_auth_callback.html for development — run with a fixed port: flutter run -d chrome --web-port 5000).

  2. Add web/yandex_auth_callback.html to your app:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Yandex auth callback</title></head>
<body>
<script>
  (function () {
    if (window.opener) {
      window.opener.postMessage(
        'yandex_login_sdk:' + window.location.search + window.location.hash,
        window.location.origin
      );
    }
    window.close();
  })();
</script>
</body>
</html>

That's it — signIn() opens the Yandex consent popup and resolves with the token; getUserInfo / getJwt work in the browser out of the box (login.yandex.ru serves CORS headers). Call signIn() from a user gesture (button tap), otherwise the browser blocks the popup (code POPUP_BLOCKED). If the app is deployed under a sub-path, set YandexLoginSdkWeb.redirectUriOverride before signing in.

Usage #

import 'package:yandex_login_sdk/yandex_login_sdk.dart';

Future<void> signIn() async {
  try {
    final result = await YandexLoginSdk.signIn(
      clientId: 'your_yandex_oauth_client_id',
      // strategy: YandexLoginStrategy.webOnly, // skip installed Yandex apps
    );
    print('Access token: ${result.token}');
    print('JWT (iOS only): ${result.jwt}');
    print('Expires at (Android only): ${result.expiresAt}');
  } on YandexAuthCancelledException {
    // User dismissed the sheet — no need to show an error.
  } on YandexAuthInProgressException {
    // A sign-in is already running — ignore the extra tap.
  } on YandexAuthUnsupportedException {
    // Web/desktop or unsupported — fall back to your own WebView.
  } on YandexAuthException catch (e) {
    print('Yandex SDK error: $e');
  }
}

Login strategy #

signIn accepts an optional strategy:

Value Android iOS Web
YandexLoginStrategy.auto (default) NATIVE → CHROME_TAB → WEBVIEW Yandex apps → web session popup (ignored)
YandexLoginStrategy.webOnly CHROME_TAB → WEBVIEW ASWebAuthenticationSession only popup (ignored)

There is deliberately no nativeOnly: neither native SDK can require the app-only flow — both fall back to the browser when no Yandex app is installed. On web there is only the popup flow, so the strategy is ignored.

Fetching the user profile #

getUserInfo is a pure-Dart call to login.yandex.ru/info — it behaves the same on every platform and needs no native support. Which fields are populated depends on the permissions your OAuth app was granted (see Scopes).

final result = await YandexLoginSdk.signIn(clientId: clientId);
final user = await YandexLoginSdk.getUserInfo(token: result.token);

print(user.displayName);   // e.g. "Vasya"
print(user.defaultEmail);  // requires login:email
print(user.avatarUrl());   // requires login:avatar; null when no avatar

Cross-platform JWT #

YandexLoginResult.jwt is only populated natively on iOS. For a JWT that is identical on both platforms, call getJwt — it fetches the signed JWT from login.yandex.ru/info?format=jwt (the same endpoint the Android SDK uses internally):

final jwt = await YandexLoginSdk.getJwt(token: result.token);

Both getUserInfo and getJwt throw YandexAuthInvalidTokenException on an expired or revoked token (HTTP 401), and both accept an optional timeout: Duration(...) that bounds the whole request (code TIMEOUT on expiry).

Signing out #

await YandexLoginSdk.signOut();
  • iOS — calls the native YandexLoginSDK.logout(), clearing the cached token/JWT, PKCE verifier and CSRF state from the Keychain. This forces the next signIn to present interactive UI (and lets the user switch accounts).
  • Android — the authsdk is stateless and has no logout, so this is a documented no-op.
  • Web — the plugin holds no session state, so this is a documented no-op too (the Yandex cookie session in the browser is not touched).

On both platforms it is local only: it does not revoke the token on Yandex's servers, nor clear the Yandex-app / browser cookie session. Drop your own copy of the token afterwards.

Scopes (permissions) #

Yandex fixes OAuth permissions when you register your app at oauth.yandex.ru. The native Yandex SDKs (3.x) do not support requesting scopes at runtime, so this plugin has no scopes argument. The YandexScope constants are provided for reference — they document which YandexUserInfo fields each permission unlocks:

Constant Scope Unlocks
YandexScope.loginInfo login:info displayName, realName, firstName, lastName, sex
YandexScope.loginEmail login:email defaultEmail, emails
YandexScope.loginAvatar login:avatar defaultAvatarId / avatarUrl()
YandexScope.loginBirthday login:birthday birthday
YandexScope.loginDefaultPhone login:default_phone defaultPhone

Logging #

The plugin emits diagnostic events through an opt-in callback — disabled by default, no print calls in release builds. While a handler is installed the native Kotlin/Swift layers mirror their own events into the same hook (prefixed with [native]), and on Android the underlying authsdk's logcat output is enabled too. Wire it up to your logger of choice:

import 'package:yandex_login_sdk/yandex_login_sdk.dart';

YandexLoginSdk.onLog = (level, message, {error, stackTrace}) {
  switch (level) {
    case YandexLogLevel.error:
      // forward to Sentry, Crashlytics, etc.
      mySentry.captureException(error, stackTrace: stackTrace, hint: message);
    case YandexLogLevel.warning:
    case YandexLogLevel.info:
    case YandexLogLevel.debug:
      myLogger.log(level.name, message);
  }
};

What you'll see during a normal flow:

Level Message
info signIn() invoked
debug Invoking native signIn (clientId length=N)
debug Native signIn returned token (length=N)
info signIn() succeeded

On cancel: info: signIn() cancelled by user. On unsupported platform: warning: signIn() unsupported on this platform. On any other error: error: signIn() failed: <code> <message> with error and stackTrace populated.

API #

YandexLoginSdk.signIn({required String clientId, YandexLoginStrategy strategy = .auto}) → Future<YandexLoginResult> #

Triggers the authorization flow. clientId is used on every platform: on Android it overrides the manifest placeholder at runtime (authsdk 3.2+), on iOS it (re-)activates the SDK, on web it goes into the OAuth URL. See Login strategy for strategy.

Web-specific error codes: POPUP_BLOCKED (call from a user gesture) and STATE_MISMATCH (CSRF-mismatched callback discarded).

YandexLoginResult #

Field Type Notes
token String OAuth 2.0 access token
jwt String? Native JWT — iOS only. For every platform use getJwt.
expiresIn int? Relative TTL in seconds from issuance — Android and Web (null on iOS)
issuedAt DateTime? When the sign-in response arrived on the Dart side
expiresAt DateTime? (getter) issuedAt + expiresIn; null on iOS

YandexLoginSdk.getUserInfo({required String token, http.Client? httpClient, Duration? timeout}) → Future<YandexUserInfo> #

Pure-Dart GET login.yandex.ru/info. Throws YandexAuthInvalidTokenException on HTTP 401 and YandexAuthException (codes HTTP_<status>, BAD_RESPONSE, TIMEOUT, CONNECTION_ERROR, BAD_ARGS) otherwise.

YandexLoginSdk.getJwt({required String token, String? jwtSecret, http.Client? httpClient, Duration? timeout}) → Future<String> #

Pure-Dart GET login.yandex.ru/info?format=jwt. Returns the raw signed JWT. jwtSecret only changes the HMAC signing key — avoid shipping a real client_secret in the app.

YandexLoginSdk.signOut() → Future<void> #

Clears local sign-in state. Real logout() on iOS; documented no-op on Android. Local-only — no server-side revocation. See Signing out.

YandexUserInfo #

Field Type Notes
id / login / clientId String always present
displayName / realName / firstName / lastName / sex String? login:info
defaultEmail String? login:email
emails List<String> login:email
defaultAvatarId + avatarUrl([size]) String? login:avatar
birthday String? login:birthday, raw YYYY-MM-DD
defaultPhone YandexPhone? login:default_phone
psuid / oldSocialLogin String?
raw Map<String, dynamic> full decoded body (forward-compat)

YandexLoginSdk.onLog #

Type Notes
YandexLogHandler? Optional callback (level, message, {error, stackTrace}). null = silent. See Logging above.

Exceptions #

Exception When
YandexAuthCancelledException User dismissed the auth sheet
YandexAuthInProgressException signIn called while another sign-in is running
YandexAuthUnsupportedException Plugin not available on this platform
YandexAuthInvalidTokenException getUserInfo / getJwt got HTTP 401 (expired/revoked token)
YandexAuthException Any other SDK / configuration / network error

Testing #

The Dart layer is covered by 113 unit tests with 100 % line coverage — every error branch in the method-channel implementation, the getUserInfo / getJwt HTTP paths (success, 401, server error, timeout, transport failure, malformed body), every exception type and YandexUserInfo.fromJson edge case is exercised. Coverage is reported to Coveralls on every push and pull request.

Running tests locally #

flutter test                            # 113 tests, ~1 s
flutter test --coverage                 # writes coverage/lcov.info
genhtml coverage/lcov.info -o coverage/html && open coverage/html/index.html

CI #

Every push and every pull request runs:

  1. dart format --set-exit-if-changed . — code style gate
  2. flutter analyze — static analysis must pass
  3. flutter test --coverage — all tests must pass
  4. flutter pub publish --dry-run — packaging must be valid
  5. Build the example app for Android, iOS and Web to catch native regressions
  6. Kotlin unit tests of the Android plugin layer

What's not covered #

The native Kotlin and Swift layers are intentionally not measured — they do little more than forward calls to the official Yandex SDKs and require a real device + Yandex account to test meaningfully. Treat the example app as the manual smoke test for the native side.

Limitations / known issues #

  • expiresIn / expiresAt are null on iOS. The iOS YandexLoginSDK discards the OAuth expires_in; Android and Web populate both. (JWT parity is solved — use getJwt for an identical JWT everywhere.)
  • Desktop is not supportedsignIn throws YandexAuthUnsupportedException on macOS/Windows/Linux. getUserInfo and getJwt still work there with a token obtained elsewhere.
  • No runtime scopes. The native Yandex SDKs 3.x fix permissions at OAuth-app registration time; there is no per-login scope selection. See Scopes.
  • signOut() is local-only. It clears on-device state (iOS) or is a no-op (Android); it never revokes the token server-side or clears the cookie session.
  • No nativeOnly strategy. Neither native SDK can require the app-only flow — both fall back to the browser when no Yandex app is installed. See Login strategy.

License #

BSD-3-Clause. See LICENSE.

This plugin is a community wrapper. The bundled native code (Android and iOS) ships under Yandex's own license terms — see the Yandex LoginSDK iOS and Yandex LoginSDK Android repositories.

2
likes
160
points
537
downloads

Documentation

API reference

Publisher

verified publisherflutterfor.dev

Weekly Downloads

Native Yandex LoginSDK wrapper for Flutter — SSO via installed Yandex apps with browser fallback (iOS + Android).

Repository (GitHub)
View/report issues

Topics

#yandex #oauth #authentication #sso #login

License

BSD-3-Clause (license)

Dependencies

crypto, flutter, flutter_web_plugins, http, plugin_platform_interface, web

More

Packages that depend on yandex_login_sdk

Packages that implement yandex_login_sdk