Docs incorrect or hard to follow?

Tweet me

so I can make things better! 😊

Soundcloud Wrapper

Logged In Auth Flow

Details of the authentication flow.

Use Case

The Logged In Auth Flow should be used when the user is also logged in to your app. Access token will be stored in your DB along side a field of your choice linking the user to the token. From then on you will be able to retrieve the access token from your DB based on the logged in user and not have to make them "Connect to Soundcloud" every time they access an area of your app that needs to call the Soundcloud API.

Authentication Flow

Redirect User to Authorization URL

To begin the auth flow we must request permission from the users to call the Soundcloud API on their behalf.

Your application should construct the authorization URL with the necessary parameters and then redirect the user's browser to this URL. This step must be performed on the front-end of your application, as it involves user interaction.

import Link from "next/link"
 
// ... existing code ...
 
// btn to connect to soundcloud
const ConnectToSoundcloudButton = () => {
  // clientId: The client ID you obtained during application registration.
  const clientId = process.env.NEXT_PUBLIC_CLIENT_ID
  // redirectUri: The URI you provided during application registration. After authentication, the user will be redirected to this URI. Your application should be prepared to receive and process the authorization code at this URI.
  const redirectUri = process.env.NEXT_PUBLIC_REDIRECT_URI
  // codeChallenge: A PKCE code challenge.
  const codeChallenge = process.env.NEXT_PUBLIC_CODE_CHALLENGE
  // state: A random string to protect against CSRF attacks - generate using crypto.randomUUID()
  const state = crypto.randomUUID()
 
  return (
    <Link
      href={`https://secure.soundcloud.com/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&code_challenge=${codeChallenge}&code_challenge_method=S256&state=${state}`}
    >
      <button>Connect To Soundcloud</button>
    </Link>
  )
}
 
// ... existing code ...

If you are unsure on any of the keys above please see the Prerequisites section.

Grant Access: User Authorization

A pop-up window will be opened allowing the user to log in to SoundCloud and approve your app's authorization request. If the user is already signed into SoundCloud, they will be able to authorize your request in one click.

Obtain Access Token

If the user approves your authorization request, they will be sent to the redirect_uri you specified when registering your app. The redirectUri should have a ?code= param attached. You will need to process this however you see best and pass it to your back end where it will be used to obtain an access token.

Here you should also securely pass a userId or reference to the logged in user so we can later get the stored token based on the logged in user.

import { createToken } from "./actions"
 
// ... existing code ...
 
// nextjs home page
export default async function Home(props: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) {
  // check for params - code will be in the url after user authorizes soundcloud
  const searchParams = await props.searchParams
  const code = searchParams.code
  // if code is in the url, we know user has authorized soundcloud and can create a token
  if (code) {
    // creates token with server action
    const userData = await createToken(code as string)
    console.log(userData)
  }
 
  // connect button user will have clicked in step 1
  return (
    <div className='flex flex-col gap-4'>
      <ConnectToSoundcloudButton />
    </div>
  )
}
 
// ... existing code ...

Obtaining the access code should happen back end as their are keys invovled that should not be exposed to the client. Create your own endpoint to handle obtaining acess code. The endpoint should request a token and upon success store it in your DB.

// index.ts
import Soundcloud from "soundcloud-wrapper"
 
// ... existing code ...
 
// define token schema
const tokenSchema = new mongoose.Schema({
  userId: { type: mongoose.Schema.Types.ObjectId },
  access_token: { type: String },
  token_type: { type: String },
  expires_in: { type: Number },
  refresh_token: { type: String },
  scope: { type: String },
})
 
// define token model
const Token = mongoose.model("Token", tokenSchema)
 
// initialize soundcloud client
const sc = new Soundcloud(
  process.env.SOUNDCLOUD_CLIENT_ID as string,
  process.env.SOUNDCLOUD_CLIENT_SECRET as string,
  process.env.SOUNDCLOUD_REDIRECT_URI as string,
  process.env.PKCE_CODE_VERIFIER as string
)
 
// route for getting and storing auth token
app.get("/soundcloud/token", async (req: Request, res: Response) => {
  try {
    // get token from soundcloud, req.query.code should be passed from your front end after the user has authorized the app
    const tokenRequest = await sc.token.getToken(req.query.code as string)
    // store token in db
    const token = new Token({
      // userId should be securely passed along with the request - the value here is a placeholder
      userId: new mongoose.Types.ObjectId(),
      ...tokenRequest,
    })
    await token.save()
    res.status(201).json(token)
  } catch (e) {
    res.status(500).json({ message: "Error getting token" })
  }
})
 
// ... existing code ...

Use Token

The returned object has an access_token property. To send requests to the API, retrieve the access_token from your DB and pass it to the Authorization header. Now instead of sending the user through the full authorization flow on each API interaction, use the token stored your database to authorize requests.

// index.ts
import Soundcloud from "soundcloud-wrapper"
 
// ... existing code ...
 
// route querying soundcloud api
app.get("/soundcloud/me", async (req: Request, res: Response) => {
  try {
    // userId should be securely passed along with the request - the value here is a placeholder
    const userId = "67a391e61cc9342bf0b895b1"
    //get token form DB
    const token = await Token.findOne({ userId })
    // query soundcloud api
    const me = await sc.me.me(token?.access_token as string)
    // return response
    res.status(200).json(me)
  } catch (e) {
    res.status(500).json({ message: "Error getting me" })
  }
})
 
// ... existing code ...

On this page