Problems, when trying to use the Customer Account API

Problems, when trying to use the Customer Account API

LasseC
Shopify Partner
1 0 3

Hello,

i am having trouble following the docs of the Customer Account API Customer Account API reference (shopify.dev).

My objective (correct me, if i misunderstood anything, and the objective seems odd or even impossible):
- Setting up the Headless sales channel and using the Customer Account API as a confidential client (i have set up a new shop for testing)

- Authenticating customers to the shopify backend with my Next.js app, by using the code from the docs above (i am not using Hydrogen, since i have to use Next.js for other purposes anyway)


So far, i haven't got any further than completing the authorization process. When trying to obtain the access_token, i recieve the response code 401, "invalid_client", with the following error description:

'The client identifier provided is invalid, the client failed to authenticate, the client did not include its credentials, provided multiple client credentials, or used unsupported credentials type.'

 

Obviously, i triple checked both client-id and client-secret, as well as rotating the credentials in the backend and ensuring the validity of both strings.

Here is the code of my home/login page,

 

import Head from 'next/head'
import Link from 'next/link'
import * as util from '../components/Utilities

export default function Home() {
    function loginCustomer(event) {
        event.preventDefault()

        const clientId = process.env.NEXT_PUBLIC_CLIENT_ID
        const authorizationRequestUrl = new URL(`https://shopify.com/${process.env.NEXT_PUBLIC_SHOP_ID}/auth/oauth/authorize`)
        authorizationRequestUrl.searchParams.append('scope', 'openid email https://api.customers.com/auth/customer.graphql')
        authorizationRequestUrl.searchParams.append('client_id', clientId)
        authorizationRequestUrl.searchParams.append('response_type', 'code')
        authorizationRequestUrl.searchParams.append('redirect_uri', 'http://localhost:3000/dashboard')
        authorizationRequestUrl.searchParams.append('state', util.generateState())
        
        window.location.href = authorizationRequestUrl.toString()
    }

    return (<>
        <Head>
            <meta name="description" content="Beschreibung" />
            <meta name="keywords" content="Keywords" />
            <title>Page title</title>
            <link rel="icon" href="/favicon.png" sizes="any" />
        </Head>

        <main id="page-home">
            <div className="ml-container">
                <form onSubmit={loginCustomer}>
                    <button type="submit">Login</button>
                </form>
            </div>
        </main>
    </>)
}

 

 

the dashboard,

 

import Head from 'next/head'
import Link from 'next/link'
import { useEffect } from 'react'
// schopify
import { getCredentials } from './api/accounts/auth'

export async function getServerSideProps({ query }) {
    // obtain access token
    const credentials = await getCredentials()
    const body = new URLSearchParams()
    body.append('grant_type', 'authorization_code')
    body.append('client_id', process.env.NEXT_PUBLIC_CLIENT_ID)
    body.append('redirect_uri', `http://localhost:3000/dashboard`)
    body.append('code', query.code)

    const headers = {
        'content-type': 'application/x-www-form-urlencoded',
        'Authorization': `Basic ${credentials}`
    }
console.log('Authorization: ', `Basic ${credentials}`);

    const response = await fetch(`https://shopify.com/${process.env.NEXT_PUBLIC_SHOP_ID}/auth/oauth/token`, {
        method: 'POST', headers: headers, body: body,
    })

    const test = await response.json()
// this line logs the error code 400, invalid_client
    console.log(test);
    
    return {
        props: {}
    }
}


export default function Dashboard(props) {
    function logoutCustomer() {
        console.log('logout');
    }

    return (<>
        <Head>
            <meta name="description" content="Beschreibung" />
            <meta name="keywords" content="Keywords" />
            <title>Dashboard</title>
            <link rel="icon" href="/favicon.png" sizes="any" />
        </Head>

        <main id="page-dashboard">
            <div className="ml-container">
                <h1>Dashboard</h1>
                <button onClick={logoutCustomer}>Logout</button>
            </div>
        </main>
    </>)
}

 

 and the "authorization header" functions

 

import crypto from 'crypto'

export async function getCredentials() {
    const clientId = process.env.NEXT_PUBLIC_CLIENT_ID;
    const clientSecret = process.env.NEXT_PUBLIC_CLIENT_SECRET;
    // const credentials = await crypto.subtle.digest({ name: "SHA-256" }, new TextEncoder().encode(`${clientId}:${clientSecret}`))
    // const hash = convertBufferToString(credentials);
    // return base64UrlEncode(hash);
    return await crypto.subtle.digest({ name: "SHA-256" }, new TextEncoder().encode(`${clientId}:${clientSecret}`))
}

function base64UrlEncode(string) {
    const base64 = btoa(string);
    // This is to ensure that the encoding does not have +, /, or = characters in it.
    return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}

function convertBufferToString(ArrayBuffer) {
    const uintArray = new Uint8Array(ArrayBuffer);
    const numberArray = Array.from(uintArray);
    return String.fromCharCode(...numberArray);
}

 

Note, that i tried converting the arrayBuffer back to a string as well, since it didn't seem right to me in the documentation.

I know that the Customer Account API is brand new, but since i don't see any alternative in the docs, i have to keep trying for now. Any help would be well apreciated.

Thanks in advance and
best regards

Replies 11 (11)

KevweAmena
Shopify Partner
1 0 0

I'm facing a similar issue, doesn't seem to have any other alternative in the docs either 

ArtemFokin
Shopify Partner
19 0 3

Hello, any updates here? same issue

futbolpal
Shopify Partner
5 0 2

I believe I got this working by using client_id/client_secret in the body, with no Authorization header.

 

  body.append('grant_type', 'urn:ietf:params:oauth:grant-type:token-exchange')
  body.append('client_id', clientId)
  body.append('client_secret', clientSecret)
  body.append('audience', customerApiClientId)
  body.append('subject_token', accessToken)
  body.append('subject_token_type', 'urn:ietf:params:oauth:token-type:access_token')
  body.append('scopes', 'https://api.customers.com/auth/customer.graphql')
mathemagics
Shopify Partner
1 0 0

You probably don't want to be exposing your client secret in an http request like this

futbolpal
Shopify Partner
5 0 2

i don't think that is a concern, as this is a service to service call.  being part of the body, it is protected by SSL

futbolpal
Shopify Partner
5 0 2

but to your point, you are correct in the case of performing these from a front end

marnau
Shopify Partner
9 0 3

I'm trying to implement the passwordless login but I'm getting an "access denied" error@futbolpal I would greatly appreciate if you could share a working example. I'd be willing to compensate economically, because I really need this done. Thank you!

randal923
Shopify Partner
7 1 3

I also was only able to do this the way you did. I checked my network requests and my client secret is not exposed, only my client id, which I think is fine. The documentation seems to be wrong https://shopify.dev/docs/api/customer. This is really annoying and they need to fix it. What are the credentials and nonce for? I still don't know. It says I can retrieve the nonce but what for?

Vysakh
Shopify Partner
1 0 0

I'm facing the same issue after deploying in Vercel. I am getting the acces_token in local.

In prod env, I am getting the response 

{"error":"invalid_request","error_description":"'client_id', 'grant_type' required."}

 

ArtemFokin
Shopify Partner
19 0 3

may be you forgot 

body.append("grant_type", "authorization_code")
?
can you share minimal example of what you have local?

randal923
Shopify Partner
7 1 3

I just want to let you know that I was doing it this way as well but then I found this https://shopify.dev/docs/custom-storefronts/building-with-the-storefront-api/customer-accounts. It's a lot better to do the graphql way from the documentation above. You don't need to redirect the user to a different page and you can easily implement Google, Apple, etc login as well.