--- title: Authenticate checkouts with the Customer Account API description: >- Learn how to authenticate checkouts using Checkout Kit and the Customer Account API. source_url: html: >- https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts md: >- https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md --- ExpandOn this page * [What you'll learn](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#requirements) * [Step 1: Get access to the Customer Account API](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-1-get-access-to-the-customer-account-api) * [Step 2: Open the login page in a Web​View](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-2-open-the-login-page-in-a-webview) * [Step 3: Handle the authorization code callback](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-3-handle-the-authorization-code-callback) * [Step 4: Exchange authorization code for an access token](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-4-exchange-authorization-code-for-an-access-token) * [Step 5: Attach the access token to your cart](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-5-attach-the-access-token-to-your-cart) * [Step 6: Show the checkout using Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-6-show-the-checkout-using-checkout-kit) * [Step 7: Logging out](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-7-logging-out) # Authenticate checkouts with the Customer Account API You can create an authenticated checkout experience for buyers within your mobile app by using the Checkout Kit and the Customer Account API. The checkout will be prefilled with the buyer's customer account information, making it easier and faster to complete checkout. To get started, follow the step-by-step example provided in this guide. *** ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Obtain a Customer Account API access token * Use the access token to create an authenticated cart * Present the checkout to a buyer using Checkout Kit The following sequence diagram illustrates the complete flow covered in the following steps. ![Steps for authenticating checkouts in a mobile app](https://shopify.dev/assets/assets/images/custom-storefronts/csk-auth.md-1-dWlrYKLk.svg) *** ## Requirements * Your store is using [new customer accounts](https://shopify.dev/docs/storefronts/headless/building-with-the-customer-account-api/getting-started#step-1-enable-customer-accounts). * You have an app using Checkout Kit. * Your app has [Storefront API access](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/getting-started#step-1-enable-storefront-api-access). *** ## Step 1: Get access to the Customer Account API Configure [Customer Account API access](https://shopify.dev/docs/storefronts/headless/building-with-the-customer-account-api/getting-started#step-2-configure-customer-account-api-access) for your app. Note Make sure that you've configured Customer Account API access as a public mobile client. Take a note of the callback URL and client ID as you'll need this information in the following step. *** ## Step 2: Open the login page in a Web​View To authenticate with the Customer Account API, your app needs to implement an [OAuth 2.0 authorization flow](https://shopify.dev/docs/storefronts/headless/building-with-the-customer-account-api/getting-started#step-3-perform-authorization-requests-and-make-queries). Mobile clients can begin the authorization flow by opening the [authorization endpoint](https://shopify.dev/docs/api/customer#step-authorization) in a WebView. When building the URL, the `client_id` parameter should match the mobile client you created in the previous step. The `redirect_uri` should also match the callback URL specified for that client. By default, the format is `shop..app://callback`. The `code_challenge` and `code_challenge_method` parameters are also required for mobile clients. After the URL has been built, open the URL in a WebView for the customer to complete login. ### Generate a code challenge and code verifier A code challenge and verifier will be used to verify that the client requesting tokens is the same client that initiated the authorization request. For more information, refer to [Proof Key for Code Exchange](https://datatracker.ietf.org/doc/html/rfc7636). The code snippets below show how you can generate a code challenge and code verifier. Save a reference to the code verifier as you'll use it in [Step 4](#step-4-exchange-authorization-code-for-an-access-token). ## Generate code challenge and code verifier ```swift func generateCodeVerifier() -> String { var buffer = [UInt8](repeating: 0, count: 32) _ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer) return base64Encode(Data(buffer)) } func generateCodeChallenge(for codeVerifier: String) -> String { guard let data = codeVerifier.data(using: .utf8) else { fatalError() } var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) data.withUnsafeBytes { bytes in _ = CC_SHA256(bytes.baseAddress, CC_LONG(data.count), &digest) } return base64Encode(Data(digest)) } ``` ```kotlin fun generateCodeVerifier(): String { val buffer = ByteArray(32) SecureRandom().nextBytes(buffer) return buffer.base64UrlEncode() } fun generateCodeChallenge(codeVerifier: String): String { val data = codeVerifier.toByteArray(Charsets.UTF_8) val digest = data.sha256() return digest.base64UrlEncode() } ``` ```javascript import * as Crypto from 'expo-crypto'; function encodeBase64(bytes: string){ return Buffer.from(bytes).toString('base64') } export async function generateCodeVerifier() { const bytes = await Crypto.getRandomBytesAsync(32); return encodeBase64(bytes); } export async function generateCodeChallenge(codeVerifier: string) { const digestString = await Crypto.digestStringAsync( Crypto.CryptoDigestAlgorithm.SHA512, codeVerifier, ); return encodeBase64(digestString); } ``` ##### iOS ``` func generateCodeVerifier() -> String { var buffer = [UInt8](repeating: 0, count: 32) _ = SecRandomCopyBytes(kSecRandomDefault, buffer.count, &buffer) return base64Encode(Data(buffer)) } func generateCodeChallenge(for codeVerifier: String) -> String { guard let data = codeVerifier.data(using: .utf8) else { fatalError() } var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) data.withUnsafeBytes { bytes in _ = CC_SHA256(bytes.baseAddress, CC_LONG(data.count), &digest) } return base64Encode(Data(digest)) } ``` ##### Android ``` fun generateCodeVerifier(): String { val buffer = ByteArray(32) SecureRandom().nextBytes(buffer) return buffer.base64UrlEncode() } fun generateCodeChallenge(codeVerifier: String): String { val data = codeVerifier.toByteArray(Charsets.UTF_8) val digest = data.sha256() return digest.base64UrlEncode() } ``` ##### React Native ``` import * as Crypto from 'expo-crypto'; function encodeBase64(bytes: string){ return Buffer.from(bytes).toString('base64') } export async function generateCodeVerifier() { const bytes = await Crypto.getRandomBytesAsync(32); return encodeBase64(bytes); } export async function generateCodeChallenge(codeVerifier: string) { const digestString = await Crypto.digestStringAsync( Crypto.CryptoDigestAlgorithm.SHA512, codeVerifier, ); return encodeBase64(digestString); } ``` ### Discovering authentication endpoints Before building the authorization URL, discover the authentication endpoints for your shop using the OpenID configuration endpoint: ## Discover authentication endpoints ```swift func discoverAuthEndpoints() async throws -> (String, String) { let discoveryUrl = URL(string: "https://\(shopDomain)/.well-known/openid-configuration")! let (data, _) = try await URLSession.shared.data(from: discoveryUrl) let config = try JSONSerialization.jsonObject(with: data) as! [String: Any] let authEndpoint = config["authorization_endpoint"] as! String let tokenEndpoint = config["token_endpoint"] as! String return (authEndpoint, tokenEndpoint) } ``` ```kotlin suspend fun discoverAuthEndpoints(): Pair { val discoveryUrl = "https://$shopDomain/.well-known/openid-configuration" val response = httpClient.get(discoveryUrl) val config = response.body>() val authEndpoint = config["authorization_endpoint"]!! val tokenEndpoint = config["token_endpoint"]!! return Pair(authEndpoint, tokenEndpoint) } ``` ```javascript async function discoverAuthEndpoints() { const discoveryUrl = `https://${shopDomain}/.well-known/openid-configuration`; const response = await fetch(discoveryUrl); const config = await response.json(); return { authEndpoint: config.authorization_endpoint, tokenEndpoint: config.token_endpoint }; } ``` ##### iOS ``` func discoverAuthEndpoints() async throws -> (String, String) { let discoveryUrl = URL(string: "https://\(shopDomain)/.well-known/openid-configuration")! let (data, _) = try await URLSession.shared.data(from: discoveryUrl) let config = try JSONSerialization.jsonObject(with: data) as! [String: Any] let authEndpoint = config["authorization_endpoint"] as! String let tokenEndpoint = config["token_endpoint"] as! String return (authEndpoint, tokenEndpoint) } ``` ##### Android ``` suspend fun discoverAuthEndpoints(): Pair { val discoveryUrl = "https://$shopDomain/.well-known/openid-configuration" val response = httpClient.get(discoveryUrl) val config = response.body>() val authEndpoint = config["authorization_endpoint"]!! val tokenEndpoint = config["token_endpoint"]!! return Pair(authEndpoint, tokenEndpoint) } ``` ##### React Native ``` async function discoverAuthEndpoints() { const discoveryUrl = `https://${shopDomain}/.well-known/openid-configuration`; const response = await fetch(discoveryUrl); const config = await response.json(); return { authEndpoint: config.authorization_endpoint, tokenEndpoint: config.token_endpoint }; } ``` ### Building the authorization URL After you've discovered the authentication endpoints and generated the code challenge and code verifier, you can build a complete authorization URL. ## Build an authorization URL ```swift let codeChallenge = generateCodeChallenge(for: codeVerifier) let (authEndpoint, _) = try await discoverAuthEndpoints() var components = URLComponents(string: authEndpoint) components?.queryItems = [ URLQueryItem(name: "scope", value: "openid email customer-account-api:full"), URLQueryItem(name: "client_id", value: customerAccountsApiClientId), URLQueryItem(name: "response_type", value: "code"), URLQueryItem(name: "redirect_uri", value: customerAccountsApiRedirectUri), URLQueryItem(name: "state", value: generateRandomString(length: 36)), URLQueryItem(name: "code_challenge", value: codeChallenge), URLQueryItem(name: "code_challenge_method", value: "S256") ] return components?.url?.absoluteString } ``` ```kotlin suspend fun buildAuthorizationUrl(codeVerifier: String): String { val codeChallenge = generateCodeChallenge(codeVerifier) val (authEndpoint, _) = discoverAuthEndpoints() return Uri.parse(authEndpoint).buildUpon() .appendQueryParameter("scope", "openid email customer-account-api:full") .appendQueryParameter("client_id", customerAccountsApiClientId) .appendQueryParameter("response_type", "code") .appendQueryParameter("redirect_uri", customerAccountsApiRedirectUri) .appendQueryParameter("state", generateRandomString(length = 36)) .appendQueryParameter("code_challenge", codeChallenge) .appendQueryParameter("code_challenge_method", "S256") .build() .toString() } ``` ```javascript async function buildAuthorizationUrl(codeVerifier) { const codeChallenge = generateCodeChallenge(codeVerifier); const { authEndpoint } = await discoverAuthEndpoints(); const url = new URL(authEndpoint); url.searchParams.append('scope', 'openid email customer-account-api:full'); url.searchParams.append('client_id', customerAccountsApiClientId); url.searchParams.append('response_type', 'code'); url.searchParams.append('redirect_uri', customerAccountsApiRedirectUri); url.searchParams.append('state', generateRandomString(36)); url.searchParams.append('code_challenge', codeChallenge); url.searchParams.append('code_challenge_method', 'S256'); return url.toString(); } ``` ##### iOS ``` let codeChallenge = generateCodeChallenge(for: codeVerifier) let (authEndpoint, _) = try await discoverAuthEndpoints() var components = URLComponents(string: authEndpoint) components?.queryItems = [ URLQueryItem(name: "scope", value: "openid email customer-account-api:full"), URLQueryItem(name: "client_id", value: customerAccountsApiClientId), URLQueryItem(name: "response_type", value: "code"), URLQueryItem(name: "redirect_uri", value: customerAccountsApiRedirectUri), URLQueryItem(name: "state", value: generateRandomString(length: 36)), URLQueryItem(name: "code_challenge", value: codeChallenge), URLQueryItem(name: "code_challenge_method", value: "S256") ] return components?.url?.absoluteString } ``` ##### Android ``` suspend fun buildAuthorizationUrl(codeVerifier: String): String { val codeChallenge = generateCodeChallenge(codeVerifier) val (authEndpoint, _) = discoverAuthEndpoints() return Uri.parse(authEndpoint).buildUpon() .appendQueryParameter("scope", "openid email customer-account-api:full") .appendQueryParameter("client_id", customerAccountsApiClientId) .appendQueryParameter("response_type", "code") .appendQueryParameter("redirect_uri", customerAccountsApiRedirectUri) .appendQueryParameter("state", generateRandomString(length = 36)) .appendQueryParameter("code_challenge", codeChallenge) .appendQueryParameter("code_challenge_method", "S256") .build() .toString() } ``` ##### React Native ``` async function buildAuthorizationUrl(codeVerifier) { const codeChallenge = generateCodeChallenge(codeVerifier); const { authEndpoint } = await discoverAuthEndpoints(); const url = new URL(authEndpoint); url.searchParams.append('scope', 'openid email customer-account-api:full'); url.searchParams.append('client_id', customerAccountsApiClientId); url.searchParams.append('response_type', 'code'); url.searchParams.append('redirect_uri', customerAccountsApiRedirectUri); url.searchParams.append('state', generateRandomString(36)); url.searchParams.append('code_challenge', codeChallenge); url.searchParams.append('code_challenge_method', 'S256'); return url.toString(); } ``` *** ## Step 3: Handle the authorization code callback After the customer successfully logs in, the WebView is redirected to the specified `redirect_uri` with a `code` query parameter. Clients should listen for page navigation events within the login WebView and extract the authorization code from the URL so that it can be used in [Step 4](#step-4-exchange-authorization-code-for-an-access-token). After the `code` has been extracted, the login WebView can be closed. ## Handle the authorization code callback ```swift func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard let url = action.request.url else { decisionHandler(.allow) return } guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let configuredComponents = URLComponents(string: customerAccountsApiRedirectUri), components.scheme == configuredComponents.scheme, components.host == configuredComponents.host else { decisionHandler(.allow) return } guard let queryItems = components.queryItems else { decisionHandler(.allow) return } guard let state = queryItems.first(where: { $0.name == "state" })?.value, state == savedState else { decisionHandler(.allow) return } guard let code = queryItems.first(where: { $0.name == "code" })?.value else { decisionHandler(.allow) return } // Use the code and cancel the navigation since we've handled the callback exchangeCodeForTokens(code: code) decisionHandler(.cancel) } ``` ```kotlin if ("${request?.url?.scheme}://${request?.url?.host}" != customerAccountsApiRedirectUri) { return super.shouldOverrideUrlLoading(view, request) } val code = request.url.getQueryParameter("code") ?: return super.shouldOverrideUrlLoading(view, request) // use the code and cancel the navigation since we've handled the callback exchangeCodeForTokens(code) return true } ``` ```javascript function handleNavigationStateChange(navState) { const { url } = navState; if (url?.startsWith(configuredRedirectUri)) { const code = new URL(url).searchParams.get('code'); if (code) { // use the code and cancel the navigation since we've handled the callback exchangeCodeForTokens(code); return false; } } return true; } ``` ##### iOS ``` func webView(_ webView: WKWebView, decidePolicyFor action: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { guard let url = action.request.url else { decisionHandler(.allow) return } guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true), let configuredComponents = URLComponents(string: customerAccountsApiRedirectUri), components.scheme == configuredComponents.scheme, components.host == configuredComponents.host else { decisionHandler(.allow) return } guard let queryItems = components.queryItems else { decisionHandler(.allow) return } guard let state = queryItems.first(where: { $0.name == "state" })?.value, state == savedState else { decisionHandler(.allow) return } guard let code = queryItems.first(where: { $0.name == "code" })?.value else { decisionHandler(.allow) return } // Use the code and cancel the navigation since we've handled the callback exchangeCodeForTokens(code: code) decisionHandler(.cancel) } ``` ##### Android ``` if ("${request?.url?.scheme}://${request?.url?.host}" != customerAccountsApiRedirectUri) { return super.shouldOverrideUrlLoading(view, request) } val code = request.url.getQueryParameter("code") ?: return super.shouldOverrideUrlLoading(view, request) // use the code and cancel the navigation since we've handled the callback exchangeCodeForTokens(code) return true } ``` ##### React Native ``` function handleNavigationStateChange(navState) { const { url } = navState; if (url?.startsWith(configuredRedirectUri)) { const code = new URL(url).searchParams.get('code'); if (code) { // use the code and cancel the navigation since we've handled the callback exchangeCodeForTokens(code); return false; } } return true; } ``` *** ## Step 4: Exchange authorization code for an access token After you've extracted the `code` parameter from the callback URL, you can submit it, along with the `code_verifier` saved earlier, to the token endpoint to [obtain a Customer Account API access token](https://shopify.dev/docs/api/customer#step-obtain-access-token). ## Exchange authorization code for a Customer Account API access token ```swift private func requestAccessToken(code: String, codeVerifier: String) async throws -> OAuthTokenResult { let (_, tokenEndpoint) = try await discoverAuthEndpoints() let parameters: [String: String] = [ "grant_type": "authorization_code", "client_id": clientId, "redirect_uri": redirectUri, "code": code, "code_verifier": codeVerifier ] let requestBody = try! JSONSerialization.data(withJSONObject: parameters, options: []) var request = URLRequest(url: URL(string: tokenEndpoint)!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpBody = requestBody return try await executeOAuthTokenRequest(request: request) } ``` ```kotlin suspend fun requestAccessToken(code: String, codeVerifier: String): OAuthTokenResult { val (_, tokenEndpoint) = discoverAuthEndpoints() val requestBody = FormBody.Builder() .add("grant_type", "authorization_code") .add("client_id", clientId) .add("redirect_uri", redirectUri) .add("code", code) .add("code_verifier", codeVerifier) .build() val request = Request.Builder() .url(tokenEndpoint) .post(requestBody) .addHeader("Content-Type", "application/x-www-form-urlencoded") .build() return executeOAuthTokenRequest(request) } ``` ```javascript export async function requestAccessToken(code, codeVerifier) { const { tokenEndpoint } = await discoverAuthEndpoints(); const requestBody = new URLSearchParams({ grant_type: 'authorization_code', client_id: clientId, redirect_uri: redirectUri, code: code, code_verifier: codeVerifier, }); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: requestBody.toString(), }); return handleOAuthTokenResponse(response); } ``` ##### iOS ``` private func requestAccessToken(code: String, codeVerifier: String) async throws -> OAuthTokenResult { let (_, tokenEndpoint) = try await discoverAuthEndpoints() let parameters: [String: String] = [ "grant_type": "authorization_code", "client_id": clientId, "redirect_uri": redirectUri, "code": code, "code_verifier": codeVerifier ] let requestBody = try! JSONSerialization.data(withJSONObject: parameters, options: []) var request = URLRequest(url: URL(string: tokenEndpoint)!) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpBody = requestBody return try await executeOAuthTokenRequest(request: request) } ``` ##### Android ``` suspend fun requestAccessToken(code: String, codeVerifier: String): OAuthTokenResult { val (_, tokenEndpoint) = discoverAuthEndpoints() val requestBody = FormBody.Builder() .add("grant_type", "authorization_code") .add("client_id", clientId) .add("redirect_uri", redirectUri) .add("code", code) .add("code_verifier", codeVerifier) .build() val request = Request.Builder() .url(tokenEndpoint) .post(requestBody) .addHeader("Content-Type", "application/x-www-form-urlencoded") .build() return executeOAuthTokenRequest(request) } ``` ##### React Native ``` export async function requestAccessToken(code, codeVerifier) { const { tokenEndpoint } = await discoverAuthEndpoints(); const requestBody = new URLSearchParams({ grant_type: 'authorization_code', client_id: clientId, redirect_uri: redirectUri, code: code, code_verifier: codeVerifier, }); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: requestBody.toString(), }); return handleOAuthTokenResponse(response); } ``` ### Token refresh Customer Account API access tokens have an expiry time in seconds (found in the `expires_in` field of the token endpoint response). After an access token expires, it's invalid and needs to be refreshed. You can refresh an access token using the `refresh_token` (also returned in the token endpoint response) by making a [POST request](https://shopify.dev/docs/api/customer#step-using-refresh-token) to the token endpoint with: * `grant_type`: "refresh\_token" * `refresh_token`: The refresh token from the original response * `client_id`: Your Customer Account API client ID ### Token security Be sure to securely store any retrieved tokens using security features offered by the operating system. Examples: * [KeyChain](https://developer.apple.com/documentation/security/keychain-services) in iOS * [Keystore](https://developer.android.com/privacy-and-security/keystore) or [Encrypted Shared Preferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences) in Android * [SecureStore](https://docs.expo.dev/versions/latest/sdk/securestore/) or [AsyncStorage with encryption](https://github.com/emitter-io/react-native-encrypted-storage) in React Native Security Warning Never store tokens in plain text or insecure locations. Compromised tokens could allow unauthorized access to customer accounts. *** ## Step 5: Attach the access token to your cart When you create or update a cart using the Storefront API, the token obtained in [Step 4](#step-4-exchange-authorization-code-for-an-access-token) should be included in the [buyer identity input](https://shopify.dev/docs/api/storefront/2025-01/input-objects/CartBuyerIdentityInput#field-customeraccesstoken) of your cart mutation requests. Note The token endpoint response has multiple fields (including `access_token`, `refresh_token`, and `expires_in`), the token to submit in cart mutation requests is found in the `access_token` field. ## POST https\://{shopDomain}/api/{api\_version}/graphql.json ## GraphQL mutation ```graphql mutation cartCreate($input: CartCreateInput!) { cartCreate(input: $input) { checkoutUrl userErrors { field message } } } ``` ## Variables ```json { "input": { "lines": [ { "merchandiseId": "gid://shopify/ProductVariant/12345", "quantity": 1 } ], "buyerIdentity": { "customerAccessToken": "" } } } ``` Caution The `checkoutUrl` returned from cart mutation requests should be used within a short time frame. *** ## Step 6: Show the checkout using Checkout Kit Storefront API cart mutations will return a `checkoutUrl` that you can submit to Checkout Kit's `present()` or `preload()` methods to display the authenticated checkout to the buyer. ## Present checkout using Checkout Kit ```swift import ShopifyCheckoutSheetKit ShopifyCheckoutSheetKit.present(checkout: checkoutUrl, from: rootViewController, delegate: self) ``` ```kotlin import com.shopify.checkoutsheetkit.ShopifyCheckoutSheetKit ShopifyCheckoutSheetKit.present(checkoutUrl, activity, eventProcessor) ``` ```javascript import {ShopifyCheckoutSheetProvider} from '@shopify/checkout-sheet-kit'; const shopifyCheckout = useShopifyCheckoutSheet(); shopifyCheckout.present(checkoutUrl); ``` ##### iOS ``` import ShopifyCheckoutSheetKit ShopifyCheckoutSheetKit.present(checkout: checkoutUrl, from: rootViewController, delegate: self) ``` ##### Android ``` import com.shopify.checkoutsheetkit.ShopifyCheckoutSheetKit ShopifyCheckoutSheetKit.present(checkoutUrl, activity, eventProcessor) ``` ##### React Native ``` import {ShopifyCheckoutSheetProvider} from '@shopify/checkout-sheet-kit'; const shopifyCheckout = useShopifyCheckoutSheet(); shopifyCheckout.present(checkoutUrl); ``` More details can be found within the [repository](https://shopify.dev/docs/storefronts/mobile/checkout-kit#repositories) READMEs. *** ## Step 7: Logging out To allow users to logout of your app, you can make a request to the [logout endpoint](https://shopify.dev/docs/api/customer#logging-out). This is also the time to clear up any related state in your app, such as locally stored tokens and existing authenticated carts. *** * [What you'll learn](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#requirements) * [Step 1: Get access to the Customer Account API](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-1-get-access-to-the-customer-account-api) * [Step 2: Open the login page in a Web​View](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-2-open-the-login-page-in-a-webview) * [Step 3: Handle the authorization code callback](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-3-handle-the-authorization-code-callback) * [Step 4: Exchange authorization code for an access token](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-4-exchange-authorization-code-for-an-access-token) * [Step 5: Attach the access token to your cart](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-5-attach-the-access-token-to-your-cart) * [Step 6: Show the checkout using Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-6-show-the-checkout-using-checkout-kit) * [Step 7: Logging out](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts.md#step-7-logging-out)