2
votes

I am trying to fetch a Spotify API access token. As per these instructions from the Spotify API site:

The request will include parameters in the request body:

  • grant_type - Set to “client_credentials”.

The header of this POST request must contain the following parameter:

  • Authorization - A Base 64 encoded string that contains the client ID and client secret key. The field must have the format: Authorization: Basic <base64 encoded client_id:client_secret>

This is usually done by making a POST request to accounts.spotify.com like so:

curl -X "POST" -H "Authorization: Basic ZjM4ZjAw...WY0MzE=" -d grant_type=client_credentials https://accounts.spotify.com/api/token

Yielding:
{ "access_token": "NgCXRKc...MzYjw", "token_type": "bearer", "expires_in": 3600, }

I am trying to implement this in Racket like so:

http.rkt

#lang racket

(require net/http-client net/uri-codec)

(define (post hc endpoint data headers)
  (define data-encoded (alist->form-urlencoded data))
  (http-conn-send!
    hc endpoint
    #:method "POST"
    #:data data-encoded
    #:headers
      (list
        "Content-Type: application/x-www-form-urlencoded"
        (format "Content-Length: ~a" (string-length data-encoded))
        headers)))

(provide (all-defined-out))

client.rkt

#lang racket

(require
  net/base64 net/http-client net/url-connect
  racket/bytes racket/port
  "http.rkt")

(struct spotify-client (id secret))

(define (hc) (http-conn-open "accounts.spotify.com" #:ssl? #t))

(define (gen-access-token hc client)
  (let* ([credentials (string-append (spotify-client-id client) ":" (spotify-client-secret client))]
        [enc (bytes->string/utf-8 (base64-encode (string->bytes/utf-8 credentials)))]
        [auth (string-append "Authorization: Basic " enc)]
        [data (list (cons 'grant_type "client_credentials"))])
    (post hc "/api/token" data auth)
    (let-values ([(status-line header-list in-port) (http-conn-recv! hc)])
      (displayln (port->string in-port)))))

(provide (all-defined-out))

main.rkt

#lang racket

(require "client.rkt")

(let ([client (spotify-client "<client-id>" "<client-secret>")]
      [hc (hc)])
  (gen-access-token hc client))

But the response I receive from the Spotify API is

{"error":"unsupported_grant_type","error_description":"grant_type must be client_credentials, authorization_code or refresh_token"}.

This is odd because it's telling me that my grant_type is unsupported even though my grant_type is client_credentials (see when I call alist->form-urlencoded on (list (cons 'grant_type "client_credentials"))). What could be causing this?

1
Does spotify have a testing token I can use for testing out my answer? Or am I going to have to request one?Leif Andersen
(Either way is fine. :) )Leif Andersen
@LeifAndersen I don't believe there's a testing token - you'd have to acquire a client-id and client-secret from the spotify developer site.lanour

1 Answers

0
votes

Update: I couldn't get the code working using Racket's HTTP library, but it worked when I switched to using the simple-http package:

(define spotify
  (update-headers
    (update-ssl
      (update-host json-requester "accounts.spotify.com") #t)
   (list
    "Content-Type: application/x-www-form-urlencoded"
    "Authorization: Basic ...")))
(define response (json-response-body (post spotify "/api/token" #:data "grant_type=client_credentials")))
(displayln (hash-ref response 'access_token))