Skip to content

Auth with AuthorizationCodeWithPKCEStrategy method not working as per Demo shows #138

@Jesuscc9

Description

@Jesuscc9

I tried multiple ways to set up a fully client-side Auth following the Demo with no success.
I am using React with Typescript, but, the following code can be adapted to any use case. Somehow, it works very good, and, definetely better than the demo at the docs, since, it was hard to adapt to most of the use-cases. I cloned the repo, copypasted the whole thing, and then, ended up with the following code:

import React, {
  ReactNode,
  createContext,
  useCallback,
  useEffect,
  useState
} from 'react'

import {
  AuthorizationCodeWithPKCEStrategy,
  Scopes,
  SpotifyApi,
  UserProfile
} from '@spotify/web-api-ts-sdk'

interface SpotifyContextType {
  user: UserProfile | null
  logout: () => void
  handleLogoutButtonClick: () => void
  handleLoginButtonClick: () => void
  sdk: SpotifyApi | null
}

export const SpotifyAuthContext = createContext<SpotifyContextType>(
  {} as SpotifyContextType
)

export const SpotifyProvider: React.FC<{ children: ReactNode }> = ({
  children
}) => {
  const [sdk, setSdk] = useState<SpotifyApi | null>(null)
  const [user, setUser] = useState<UserProfile | null>(null)

  const authenticate = useCallback(async () => {
    const auth = new AuthorizationCodeWithPKCEStrategy(
      'yourClientId',
      `${window.location.origin}/yourRedirectPath`,
      Scopes.userDetails // adatap do your scopes
    )

    const internalSdk = new SpotifyApi(auth)

    setSdk(internalSdk)

    try {
      const { authenticated } = await internalSdk.authenticate()

      if (authenticated) {
        setSdk(() => internalSdk)
      }
    } catch (e: Error | unknown) {
      const error = e as Error
      if (
        error &&
        error.message &&
        error.message.includes('No verifier found in cache')
      ) {
        console.error(
          "If you are seeing this error in a React Development Environment it's because React calls useEffect twice. Using the Spotify SDK performs a token exchange that is only valid once, so React re-rendering this component will result in a second, failed authentication. This will not impact your production applications (or anything running outside of Strict Mode - which is designed for debugging components).",
          error
        )
      } else {
        console.error(e)
      }
    }
  }, [])

  const handleLoginButtonClick = useCallback(async () => {
    authenticate()
  }, [authenticate])

  useEffect(() => {
    if (location.search.includes('code=') && sdk === null) {
      authenticate()
      return
    }

    const storageToken = JSON.parse(
      localStorage.getItem(
        'spotify-sdk:AuthorizationCodeWithPKCEStrategy:token'
      ) ?? '{}'
    )

    const hasToken = Boolean(storageToken?.access_token)

    if (hasToken && user === null && sdk === null) {
      authenticate()
      return
    }
  }, [sdk, user, authenticate, location.search])

  useEffect(() => {
    if (user !== null) return
    if (sdk === null) return

    const fetchUser = async () => {
      try {
        const user = await sdk.currentUser.profile()
        setUser(user)
      } catch (e: Error | unknown) {}
    }
    fetchUser()
  }, [sdk, user])

  const logout = () => {
    sdk?.logOut()
    setUser(null)
    setSdk(null)
  }

  const handleLogoutButtonClick = () => {
    logout()
  }

  const value = {
    user,
    logout,
    handleLogoutButtonClick,
    handleLoginButtonClick,
    sdk
  }

  return (
    <SpotifyAuthContext.Provider value={value}>
      {children}
    </SpotifyAuthContext.Provider>
  )
}

Live demo at:
https://mettracks.com

Please let me know if this is useful for you, if you have any issue or suggestion. If I this is helpful, I'll raise a PR to update the docs with a more generic approach following this same logic

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions