Konfigurowanie przepływu poświadczeń hasła właściciela zasobu w usłudze Azure Active Directory B2C

Przed rozpoczęciem użyj selektora Wybierz typ zasad, aby wybrać typ konfigurowanych zasad. Usługa Azure Active Directory B2C oferuje dwie metody definiowania sposobu interakcji użytkowników z aplikacjami: za pomocą wstępnie zdefiniowanych przepływów użytkowników lub w pełni konfigurowalnych zasad niestandardowych. Kroki wymagane w tym artykule są różne dla każdej metody.

W usłudze Azure Active Directory B2C (Azure AD B2C) przepływ poświadczeń hasła właściciela zasobu (ROPC) to standardowy przepływ uwierzytelniania OAuth. W tym przepływie aplikacja, znana również jako jednostka uzależniona, wymienia prawidłowe poświadczenia tokenów. Poświadczenia obejmują identyfikator użytkownika i hasło. Zwrócone tokeny to token identyfikatora, token dostępu i token odświeżania.

Ostrzeżenie

Zalecamy, aby nie używać przepływu ROPC. W większości scenariuszy dostępne są bezpieczniejsze alternatywy i zalecane. Ten przepływ wymaga bardzo wysokiego poziomu zaufania w aplikacji i niesie ze sobą ryzyko, które nie są obecne w innych przepływach. Tego przepływu należy używać tylko wtedy, gdy inne bezpieczniejsze przepływy nie są opłacalne.

Uwagi dotyczące przepływu ROPC

W usłudze Azure Active Directory B2C (Azure AD B2C) są obsługiwane następujące opcje:

  • Klient natywny: interakcja użytkownika podczas uwierzytelniania odbywa się, gdy kod jest uruchamiany na urządzeniu po stronie użytkownika. Urządzenie może być aplikacją mobilną działającą w natywnym systemie operacyjnym, takim jak Android i iOS.
  • Przepływ klienta publicznego: w wywołaniu interfejsu API są wysyłane tylko poświadczenia użytkownika zebrane przez aplikację. Poświadczenia aplikacji nie są wysyłane.
  • Dodawanie nowych oświadczeń: zawartość tokenu identyfikatora można zmienić, aby dodać nowe oświadczenia.

Następujące przepływy nie są obsługiwane:

  • Serwer-serwer: system ochrony tożsamości wymaga niezawodnego adresu IP zebranego z obiektu wywołującego (klienta natywnego) w ramach interakcji. W wywołaniu interfejsu API po stronie serwera jest używany tylko adres IP serwera. W przypadku przekroczenia dynamicznego progu nieudanych uwierzytelnień system ochrony tożsamości może zidentyfikować powtarzający się adres IP jako osoba atakująca.
  • Przepływ poufnego klienta: identyfikator klienta aplikacji jest weryfikowany, ale wpis tajny aplikacji nie jest weryfikowany.

W przypadku korzystania z przepływu ROPC należy wziąć pod uwagę następujące kwestie:

Rejestrowanie aplikacji

Aby zarejestrować aplikację w dzierżawie usługi Azure AD B2C, możesz użyć naszego nowego ujednoliconego środowiska rejestracji aplikacji lub środowiska starszych aplikacji. Dowiedz się więcej na temat nowego środowiska.

  1. Zaloguj się w witrynie Azure Portal.
  2. Upewnij się, że używasz katalogu zawierającego dzierżawę usługi Azure AD B2C:
    1. Wybierz ikonę Katalogi i subskrypcje na pasku narzędzi portalu.
    2. W ustawieniach portalu | Strona Katalogi i subskrypcje , znajdź katalog usługi Azure AD B2C na liście Nazwa katalogu, a następnie wybierz pozycję Przełącz.
  3. W witrynie Azure Portal wyszukaj i wybierz pozycję Azure AD B2C
  4. Wybierz pozycję Rejestracje aplikacji, a następnie wybierz pozycję Nowa rejestracja.
  5. Wprowadź nazwę aplikacji. Na przykład ROPC_Auth_app.
  6. Pozostaw pozostałe wartości w ich postaci, a następnie wybierz pozycję Zarejestruj.
  7. Zarejestruj identyfikator aplikacji (klienta) do użycia w późniejszym kroku.
  8. W obszarze Zarządzanie wybierz pozycję Uwierzytelnianie.
  9. Wybierz pozycję Wypróbuj nowe środowisko (jeśli jest wyświetlane).
  10. W obszarze Ustawienia zaawansowane i sekcja Włącz następujące przepływy mobilne i klasyczne wybierz pozycję Tak , aby traktować aplikację jako klienta publicznego. To ustawienie jest wymagane dla przepływu ROPC.
  11. Wybierz pozycję Zapisz.
  12. W menu po lewej stronie wybierz pozycję Manifest , aby otworzyć edytor manifestu.
  13. Ustaw atrybut oauth2AllowImplicitFlow na true:
    "oauth2AllowImplicitFlow": true,
    
  14. Wybierz pozycję Zapisz.

Tworzenie przepływu użytkownika właściciela zasobu

  1. Zaloguj się do witryny Azure Portal jako administrator globalny dzierżawy usługi Azure AD B2C.
  2. Jeśli masz dostęp do wielu dzierżaw, wybierz ikonę Ustawienia w górnym menu, aby przełączyć się do dzierżawy usługi Azure AD B2C z menu Katalogi i subskrypcje.
  3. W witrynie Azure Portal wyszukaj i wybierz pozycję Azure AD B2C.
  4. Wybierz pozycję Przepływy użytkownika i wybierz pozycję Nowy przepływ użytkownika.
  5. Wybierz pozycję Zaloguj się przy użyciu poświadczeń hasła właściciela zasobu (ROPC).
  6. W obszarze Wersja upewnij się, że jest zaznaczona opcja Wersja zapoznawcza , a następnie wybierz pozycję Utwórz.
  7. Podaj nazwę przepływu użytkownika, taką jak ROPC_Auth.
  8. W obszarze Oświadczenia aplikacji wybierz pozycję Pokaż więcej.
  9. Wybierz oświadczenia aplikacji, które są potrzebne dla aplikacji, takie jak Nazwa wyświetlana, Adres e-mail i Dostawca tożsamości.
  10. Wybierz OK, a następnie wybierz Utwórz.

Wstępnie wymagane

Jeśli nie zostało to zrobione, dowiedz się więcej o niestandardowym pakiecie startowym zasad w temacie Wprowadzenie do zasad niestandardowych w usłudze Active Directory B2C.

Tworzenie zasad właściciela zasobu

  1. Otwórz plik TrustFrameworkExtensions.xml.

  2. Jeśli jeszcze nie istnieje, dodaj element ClaimsSchema i jego elementy podrzędne jako pierwszy element w elemecie BuildingBlocks :

    <ClaimsSchema>
      <ClaimType Id="logonIdentifier">
        <DisplayName>User name or email address that the user can use to sign in</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="resource">
        <DisplayName>The resource parameter passes to the ROPC endpoint</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="refreshTokenIssuedOnDateTime">
        <DisplayName>An internal parameter used to determine whether the user should be permitted to authenticate again using their existing refresh token.</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
      <ClaimType Id="refreshTokensValidFromDateTime">
        <DisplayName>An internal parameter used to determine whether the user should be permitted to authenticate again using their existing refresh token.</DisplayName>
        <DataType>string</DataType>
      </ClaimType>
    </ClaimsSchema>
    
  3. Po dodaniu elementu ClaimsSchema dodaj element ClaimsTransformations i jego elementy podrzędne do elementu BuildingBlocks :

    <ClaimsTransformations>
      <ClaimsTransformation Id="CreateSubjectClaimFromObjectID" TransformationMethod="CreateStringClaim">
        <InputParameters>
          <InputParameter Id="value" DataType="string" Value="Not supported currently. Use oid claim." />
        </InputParameters>
        <OutputClaims>
          <OutputClaim ClaimTypeReferenceId="sub" TransformationClaimType="createdClaim" />
        </OutputClaims>
      </ClaimsTransformation>
    
      <ClaimsTransformation Id="AssertRefreshTokenIssuedLaterThanValidFromDate" TransformationMethod="AssertDateTimeIsGreaterThan">
        <InputClaims>
          <InputClaim ClaimTypeReferenceId="refreshTokenIssuedOnDateTime" TransformationClaimType="leftOperand" />
          <InputClaim ClaimTypeReferenceId="refreshTokensValidFromDateTime" TransformationClaimType="rightOperand" />
        </InputClaims>
        <InputParameters>
          <InputParameter Id="AssertIfEqualTo" DataType="boolean" Value="false" />
          <InputParameter Id="AssertIfRightOperandIsNotPresent" DataType="boolean" Value="true" />
        </InputParameters>
      </ClaimsTransformation>
    </ClaimsTransformations>
    
  4. Znajdź element ClaimsProvider z wartością DisplayName i Local Account SignIn dodaj następujący profil techniczny:

    <TechnicalProfile Id="ResourceOwnerPasswordCredentials-OAUTH2">
      <DisplayName>Local Account SignIn</DisplayName>
      <Protocol Name="OpenIdConnect" />
      <Metadata>
        <Item Key="UserMessageIfClaimsPrincipalDoesNotExist">We can't seem to find your account</Item>
        <Item Key="UserMessageIfInvalidPassword">Your password is incorrect</Item>
        <Item Key="UserMessageIfOldPasswordUsed">Looks like you used an old password</Item>
        <Item Key="DiscoverMetadataByTokenIssuer">true</Item>
        <Item Key="ValidTokenIssuerPrefixes">https://sts.windows.net/</Item>
        <Item Key="METADATA">https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration</Item>
        <Item Key="authorization_endpoint">https://login.microsoftonline.com/{tenant}/oauth2/token</Item>
        <Item Key="response_types">id_token</Item>
        <Item Key="response_mode">query</Item>
        <Item Key="scope">email openid</Item>
        <Item Key="grant_type">password</Item>
      </Metadata>
      <InputClaims>
        <InputClaim ClaimTypeReferenceId="logonIdentifier" PartnerClaimType="username" Required="true" DefaultValue="{OIDC:Username}"/>
        <InputClaim ClaimTypeReferenceId="password" Required="true" DefaultValue="{OIDC:Password}" />
        <InputClaim ClaimTypeReferenceId="grant_type" DefaultValue="password" />
        <InputClaim ClaimTypeReferenceId="scope" DefaultValue="openid" />
        <InputClaim ClaimTypeReferenceId="nca" PartnerClaimType="nca" DefaultValue="1" />
        <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="ProxyIdentityExperienceFrameworkAppId" />
        <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="IdentityExperienceFrameworkAppId" />
      </InputClaims>
      <OutputClaims>
        <OutputClaim ClaimTypeReferenceId="objectId" PartnerClaimType="oid" />
        <OutputClaim ClaimTypeReferenceId="userPrincipalName" PartnerClaimType="upn" />
      </OutputClaims>
      <OutputClaimsTransformations>
        <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
      </OutputClaimsTransformations>
      <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
    </TechnicalProfile>
    

    Zastąp wartość DefaultValueclient_id identyfikatorem aplikacji ProxyIdentityExperienceFramework utworzonej w samouczku dotyczącym wymagań wstępnych. Następnie zastąp wartość DefaultValueresource_id identyfikatorem aplikacji IdentityExperienceFramework utworzonej również w samouczku dotyczącym wymagań wstępnych.

  5. Dodaj następujące elementy ClaimsProvider z ich profilami technicznymi do elementu ClaimsProviders :

    <ClaimsProvider>
      <DisplayName>Azure Active Directory</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="AAD-UserReadUsingObjectId-CheckRefreshTokenDate">
          <Metadata>
            <Item Key="Operation">Read</Item>
            <Item Key="RaiseErrorIfClaimsPrincipalDoesNotExist">true</Item>
          </Metadata>
          <InputClaims>
            <InputClaim ClaimTypeReferenceId="objectId" Required="true" />
          </InputClaims>
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="refreshTokensValidFromDateTime" />
          </OutputClaims>
          <OutputClaimsTransformations>
            <OutputClaimsTransformation ReferenceId="AssertRefreshTokenIssuedLaterThanValidFromDate" />
            <OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromObjectID" />
          </OutputClaimsTransformations>
          <IncludeTechnicalProfile ReferenceId="AAD-Common" />
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    
    <ClaimsProvider>
      <DisplayName>Session Management</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="SM-RefreshTokenReadAndSetup">
          <DisplayName>Trustframework Policy Engine Refresh Token Setup Technical Profile</DisplayName>
          <Protocol Name="None" />
          <OutputClaims>
            <OutputClaim ClaimTypeReferenceId="objectId" />
            <OutputClaim ClaimTypeReferenceId="refreshTokenIssuedOnDateTime" />
          </OutputClaims>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    
    <ClaimsProvider>
      <DisplayName>Token Issuer</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="JwtIssuer">
          <Metadata>
            <!-- Point to the redeem refresh token user journey-->
            <Item Key="RefreshTokenUserJourneyId">ResourceOwnerPasswordCredentials-RedeemRefreshToken</Item>
          </Metadata>
        </TechnicalProfile>
      </TechnicalProfiles>
    </ClaimsProvider>
    
  6. Dodaj element UserJourneys i jego elementy podrzędne do elementu TrustFrameworkPolicy:

    <UserJourney Id="ResourceOwnerPasswordCredentials">
      <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="ResourceOwnerFlow" TechnicalProfileReferenceId="ResourceOwnerPasswordCredentials-OAUTH2" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
    </UserJourney>
    <UserJourney Id="ResourceOwnerPasswordCredentials-RedeemRefreshToken">
      <PreserveOriginalAssertion>false</PreserveOriginalAssertion>
      <OrchestrationSteps>
        <OrchestrationStep Order="1" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="RefreshTokenSetupExchange" TechnicalProfileReferenceId="SM-RefreshTokenReadAndSetup" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="2" Type="ClaimsExchange">
          <ClaimsExchanges>
            <ClaimsExchange Id="CheckRefreshTokenDateFromAadExchange" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId-CheckRefreshTokenDate" />
          </ClaimsExchanges>
        </OrchestrationStep>
        <OrchestrationStep Order="3" Type="SendClaims" CpimIssuerTechnicalProfileReferenceId="JwtIssuer" />
      </OrchestrationSteps>
    </UserJourney>
    
  7. Na stronie Zasady niestandardowe w dzierżawie usługi Azure AD B2C wybierz pozycję Przekaż zasady.

  8. Włącz opcję Zastąp zasady, jeśli istnieją, a następnie przejdź do pliku TrustFrameworkExtensions.xml i wybierz go.

  9. Wybierz opcję Prześlij.

Tworzenie pliku jednostki uzależnionej

Następnie zaktualizuj plik jednostki uzależnionej, który inicjuje utworzoną podróż użytkownika:

  1. Utwórz kopię pliku SignUpOrSignin.xml w katalogu roboczym i zmień jego nazwę na ROPC_Auth.xml.

  2. Otwórz nowy plik i zmień wartość atrybutu PolicyId właściwości TrustFrameworkPolicy na unikatową wartość. Identyfikator zasad to nazwa zasad. Na przykład B2C_1A_ROPC_Auth.

  3. Zmień wartość atrybutu ReferenceId w defaultUserJourney na ResourceOwnerPasswordCredentials.

  4. Zmień element OutputClaims tak, aby zawierał tylko następujące oświadczenia:

    <OutputClaim ClaimTypeReferenceId="sub" />
    <OutputClaim ClaimTypeReferenceId="objectId" />
    <OutputClaim ClaimTypeReferenceId="displayName" DefaultValue="" />
    <OutputClaim ClaimTypeReferenceId="givenName" DefaultValue="" />
    <OutputClaim ClaimTypeReferenceId="surname" DefaultValue="" />
    
  5. Na stronie Zasady niestandardowe w dzierżawie usługi Azure AD B2C wybierz pozycję Przekaż zasady.

  6. Włącz opcję Zastąp zasady, jeśli istnieją, a następnie przejdź do i wybierz plik ROPC_Auth.xml .

  7. Wybierz opcję Prześlij.

Testowanie przepływu ROPC

Użyj ulubionej aplikacji dewelopera interfejsu API, aby wygenerować wywołanie interfejsu API i przejrzeć odpowiedź na debugowanie zasad. Skonstruuj wywołanie podobne do tego przykładu z następującymi informacjami jako treścią żądania POST:

https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/B2C_1A_ROPC_Auth/oauth2/v2.0/token

  • Zastąp <tenant-name> ciąg nazwą dzierżawy usługi Azure AD B2C.
  • Zastąp B2C_1A_ROPC_Auth pełną nazwą zasad poświadczeń hasła właściciela zasobu.
Klucz Wartość
nazwa użytkownika user-account
hasło password1
grant_type hasło
zakres openid application-id offline_access
client_id application-id
response_type id_token tokenu
  • Zastąp user-account ciąg nazwą konta użytkownika w dzierżawie.
  • Zastąp password1 ciąg hasłem konta użytkownika.
  • Zastąp application-id ciąg identyfikatorem aplikacji z rejestracji ROPC_Auth_app .
  • Offline_access jest opcjonalne, jeśli chcesz otrzymać token odświeżania.

Rzeczywiste żądanie POST wygląda podobnie do następującego przykładu:

POST /<tenant-name>.onmicrosoft.com/B2C_1A_ROPC_Auth/oauth2/v2.0/token HTTP/1.1
Host: <tenant-name>.b2clogin.com
Content-Type: application/x-www-form-urlencoded

username=contosouser.outlook.com.ws&password=Passxword1&grant_type=password&scope=openid+bef22d56-552f-4a5b-b90a-1988a7d634ce+offline_access&client_id=bef22d56-552f-4a5b-b90a-1988a7d634ce&response_type=token+id_token

Pomyślna odpowiedź z dostępem w trybie offline wygląda następująco:

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9YQjNhdTNScWhUQWN6R0RWZDM5djNpTmlyTWhqN2wxMjIySnh6TmgwRlki...",
    "token_type": "Bearer",
    "expires_in": "3600",
    "refresh_token": "eyJraWQiOiJacW9pQlp2TW5pYVc2MUY0TnlfR3REVk1EVFBLbUJLb0FUcWQ1ZWFja1hBIiwidmVyIjoiMS4wIiwiemlwIjoiRGVmbGF0ZSIsInNlciI6Ij...",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik9YQjNhdTNScWhUQWN6R0RWZDM5djNpTmlyTWhqN2wxMjIySnh6TmgwRlki..."
}

Realizowanie tokenu odświeżania

Skonstruuj wywołanie POST podobne do pokazanego tutaj. Użyj informacji w poniższej tabeli jako treści żądania:

https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/B2C_1A_ROPC_Auth/oauth2/v2.0/token

  • Zastąp <tenant-name> ciąg nazwą dzierżawy usługi Azure AD B2C.
  • Zastąp B2C_1A_ROPC_Auth pełną nazwą zasad poświadczeń hasła właściciela zasobu.
Klucz Wartość
grant_type refresh_token
response_type id_token
client_id application-id
zasób application-id
refresh_token refresh-token
  • Zastąp application-id ciąg identyfikatorem aplikacji z rejestracji ROPC_Auth_app .
  • Zastąp element refresh-tokenrefresh_token , który został wysłany z powrotem w poprzedniej odpowiedzi.

Pomyślna odpowiedź wygląda następująco:

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhT...",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQn...",
    "token_type": "Bearer",
    "not_before": 1533672990,
    "expires_in": 3600,
    "expires_on": 1533676590,
    "resource": "bef2222d56-552f-4a5b-b90a-1988a7d634c3",
    "id_token_expires_in": 3600,
    "profile_info": "eyJ2ZXIiOiIxLjAiLCJ0aWQiOiI1MTZmYzA2NS1mZjM2LTRiOTMtYWE1YS1kNmVlZGE3Y2JhYzgiLCJzdWIiOm51bGwsIm5hbWUiOiJEYXZpZE11IiwicHJlZmVycmVkX3VzZXJuYW1lIjpudWxsLCJpZHAiOiJMb2NhbEFjY291bnQifQ",
    "refresh_token": "eyJraWQiOiJjcGltY29yZV8wOTI1MjAxNSIsInZlciI6IjEuMCIsInppcCI6IkRlZmxhdGUiLCJzZXIiOiIxLjAi...",
    "refresh_token_expires_in": 1209600
}

Rozwiązywanie problemów

Podana aplikacja nie jest skonfigurowana tak, aby zezwalała na niejawny przepływ "OAuth"

  • Objaw — uruchamiasz przepływ ROPC i otrzymujesz następujący komunikat: AADB2C90057: Podana aplikacja nie jest skonfigurowana tak, aby zezwalała na niejawny przepływ OAuth.
  • Możliwe przyczyny — niejawny przepływ nie jest dozwolony dla aplikacji.
  • Rozwiązanie: Podczas tworzenia rejestracji aplikacji w usłudze Azure AD B2C należy ręcznie edytować manifest aplikacji i ustawić wartość oauth2AllowImplicitFlow właściwości na true. Po skonfigurowaniu oauth2AllowImplicitFlow właściwości może upłynąć kilka minut (zazwyczaj nie więcej niż pięć), aby zmiana mogła mieć wpływ.

Korzystanie z natywnego zestawu SDK lub uwierzytelniania aplikacji

Usługa Azure AD B2C spełnia standardy protokołu OAuth 2.0 dla poświadczeń hasła właściciela zasobu klienta publicznego i powinny być zgodne z większością zestawów SDK klienta. Aby uzyskać najnowsze informacje, zobacz Zestaw SDK aplikacji natywnych dla protokołu OAuth 2.0 i OpenID Połączenie implementowanie nowoczesnych najlepszych rozwiązań.

Następne kroki

Pobierz przykłady robocze, które zostały skonfigurowane do użycia z usługą Azure AD B2C z usługi GitHub, dla systemów Android i iOS.