Welcome to the Kinde community

Updated 8 months ago

Unable to make a POST request to retrieve a token with `grant_type` of `authorization_code`

I'm attempting to follow the "Quick Start" guide on signing in users. I have successfully retrieved the CALLBACK_AUTHORIZATION_CODE, but when I attempt to request the token I get a 400 error.

I'm following this documentation: https://kinde.com/docs/developer-tools/using-kinde-without-an-sdk/#handling-the-callback

The request I'm making is to:
Plain Text
https://<your_kinde_sudomain>.kinde.com/oauth2/token
?client_id=<your_kinde_client_id>
&client_secret=<your_kinde_client_secret>
&grant_type=authorization_code
&redirect_uri=<your_app_redirect_url>
&code=<CALLBACK_AUTHORIZATION_CODE>


I have triple checked that each of the credentials being sent is correct, yet I still get a 400 response with no error message in the body of the response.

Note: Probably not super important, but my backend is written in Go, although I can't seem to get it to work even with a basic curl request:
Plain Text
curl -vv -XPOST -H "Content-Type: application/x-www-form-urlencoded" \
  "https://karehero.kinde.com/oauth2/token?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&grant_type=authorization_code&redirect_uri=http://localhost:3000&code=${CODE}"
1
j
V
A
36 comments
Here is my backend code in case anybody noticed what I'm doing wrong:
Plain Text
var tokenIssuer = "https://<my_domain>.kinde.com"
func (k *Kinde) RequestToken(code string) error {
    // make a request to the token endpoint
    req, err := http.NewRequest(
        "POST",
        fmt.Sprintf("%v/oauth2/token", tokenIssuer),
        nil,
    )
    if err != nil {
        return err
    }

    // set headers
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    // set query params
    q := req.URL.Query()
    q.Add("client_id", k.ClientID)
    q.Add("client_secret", k.ClientSecret)
    q.Add("grant_type", "authorization_code")
    q.Add("redirect_uri", "http://localhost:3000")
    q.Add("code", code)
    req.URL.RawQuery = q.Encode()

    // make the request
    client := &http.Client{}
    res, err := client.Do(req)
    if err != nil {
        return err
    }
    defer res.Body.Close()

    if res.StatusCode != http.StatusOK {
        return fmt.Errorf("error requesting token: %v", res.Status)
    }

    // parse the response
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return err
    }

    fmt.Println("Kinde response:", res, string(body))

    return nil
}
It errors on if res.StatusCode != http.StatusOK { with:
Plain Text
error requesting token: 400 Bad Request
I've also tried parsing the body, and it's always empty
Here's a node example:
Plain Text
        fetch(`${<your_domain>}.kinde.com/oauth2/token`, {
            method: "POST",
            headers: {
                "content-type": "application/x-www-form-urlencoded",
            },
            body: new URLSearchParams({
                audience: "<auth_domain>/api",
                grant_type: "authorization_code",
                client_id: <client_id>,
                client_secret: <client_secret>,
            }),
    })
Can you try passing that through and see if you get the token back?
@VKinde Yes I've tried this in postman and it does work for me. Isn't this for an M2M token though? I need the user token
Oh, but it doesn't work with authorization_code
That should be just for the access token itself, not specifically tied to M2M
It only works when the grant_type is client_credentials
Sorry, you have to pass the &code=<CALLBACK_AUTHORIZATION_CODE> at the end of that request
Plain Text
curl -v -G \
  -XPOST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=${CODE}" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "audience=https://${DOMAIN}.kinde.com/api" \
  "https://${DOMAIN}.kinde.com/oauth2/token"


This is what I tried just now
Can you double check and make sure you're passing the correct authorization code in the param?
Just generated a new one, double checked and same issue
I can't help but feel like I'm doing something stupid since I get no response body from the 400 res
Here is the verbose curl output:
Plain Text
* Host <REDACTED>.kinde.com:443 was resolved.
* IPv6: (none)
* IPv4: 18.133.18.216, 18.132.161.25
*   Trying 18.133.18.216:443...
* Connected to <REDACTED>.kinde.com (18.133.18.216) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /home/jamie/.anaconda3/ssl/cacert.pem
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519 / RSASSA-PSS
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: CN=*.kinde.com
*  start date: Mar 12 00:00:00 2024 GMT
*  expire date: Apr 11 23:59:59 2025 GMT
*  subjectAltName: host "<REDACTED>.kinde.com" matched cert's "*.kinde.com"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M03
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> POST /oauth2/token?grant_type=authorization_code&client_id=<REDACTED>&client_secret=<REDACTED>&audience=https://<REDACTED>.kinde.com/api HTTP/1.1
> Host: <REDACTED>.kinde.com
> User-Agent: curl/8.5.0
> Accept: */*
> Content-Type: application/x-www-form-urlencoded
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 400 Bad Request
< Date: Fri, 29 Mar 2024 20:52:01 GMT
< Content-Length: 0
< Connection: keep-alive
< Vary: Origin
< 
* Connection #0 to host <REDACTED>.kinde.com left intact
FYI: The app I'm using is of type "Back-end web"
Ah okay, so I tried with the node example, I'm getting an error message now
Okay, I've finally managed to get a token response in JS, just need to figure out what it's doing differently, thanks @VKinde

I'll update this thread if I figure it out
Okay I've figured it out. Turns out URL query params are not sufficient to pass the data effectively to Kinde.

Instead the params need to be sent as form data. In Go, this looks like:
Plain Text
    // set form data
    form := url.Values{}
    form.Add("client_id", k.ClientID)
    form.Add("client_secret", k.ClientSecret)
    form.Add("grant_type", "authorization_code")
    form.Add("redirect_uri", "http://localhost:3000/authenticate")
    form.Add("response_type", "code")
    form.Add("audience", "https://<REDACTED>.kinde.com/api")
    form.Add("scope", "openid profile email")
    form.Add("code", code)

    req.Body = ioutil.NopCloser(strings.NewReader(form.Encode()))
Full example for anyone that finds this thread:
Plain Text
type KindeTokenResponse struct {
    AccessToken string `json:"access_token"`
    ExpiresIn   int    `json:"expires_in"`
    IdToken     string `json:"id_token"`
    Scope       string `json:"scope"`
    TokenType   string `json:"token_type"`
}

func (k *Kinde) RequestToken(code string) (KindeTokenResponse, error) {
    // make a request to the token endpoint
    req, err := http.NewRequest(
        "POST",
        fmt.Sprintf("%v/oauth2/token", tokenIssuer),
        nil,
    )
    if err != nil {
        return KindeTokenResponse{}, err
    }

    // set headers
    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

    // set form data
    form := url.Values{}
    form.Add("client_id", k.ClientID)
    form.Add("client_secret", k.ClientSecret)
    form.Add("grant_type", "authorization_code")
    form.Add("redirect_uri", "http://localhost:3000/authenticate")
    form.Add("code", code)

    req.Body = ioutil.NopCloser(strings.NewReader(form.Encode()))

    // make the request
    client := &http.Client{}
    res, err := client.Do(req)
    if err != nil {
        return KindeTokenResponse{}, err
    }
    defer res.Body.Close()

    if res.StatusCode != http.StatusOK {
        return KindeTokenResponse{}, fmt.Errorf(
            "error requesting token: %v",
            res.Status,
        )
    }

    // parse the response
    var kindeTokenResponse KindeTokenResponse
    if err := json.NewDecoder(res.Body).Decode(&kindeTokenResponse); err != nil {
        return KindeTokenResponse{}, err
    }

    return kindeTokenResponse, nil
}
Awesome to see that you were able to figure it out @jamie , would this guide been of help? https://kinde.com/blog/engineering/how-to-use-kinde-with-golang-oss-libraries/
@onderay I came across this article really early on actually.

This article does provide useful insights into how to connect to Kinde via an M2M app, but didn't help me to understand how I can retrieve an access token for a specific user.

Having implemented both now, I may see if I can borrow ideas from this article to produce a small Go library for this.
One of the main technical differences between them is the article specified uses client_credentials, whereas the quick start recommends using authorization_code.

The implementation on the article relies quite heavily on client_credentials and so didn't seem helpful at the time.
Amazing, thanks for the feedback @jamie I will pass this along to the team. We are working on a Go SDK atm and should hopefully have it out soon.
Yes I saw, let me know if I can help at all
Amazing, I will let the team know
@jamie the golang SDK is in a non-shareable state yet, which parts are you interested in contributing to?
@ev_kinde Specifically I'd like to contribute to anything that goes towards fully implementing Kinde's API as a golang SDK. Ideally I'd like to try to abstract some of the functionality to make it more like idiomatic Go, and eventually hook into that abstraction when exposing webhooks (I realise this is a beta feature, but when released it would be nice to take advantage of them in the same lib).

We're using it in our Go API so I've already implemented the very basics, but moving forward we want much more complete integration with Kinde.
If the setup was simpler and documented I think I'd have been up and running in 30 minutes, but since there wasn't anything Go specific, I ended up spending maybe a couple of days figuring it all out start to finish.

It would have been useful to know that the information sent to the /oauth2/token endpoint needs to be POST form data, the doc is a little misleading as it appears that I could perhaps make a curl request (with encoded query parameters) using the format shown in the code example:
Attachment
image.png
Maybe it would be worth looking at what a curl command for that might look like?
I'll have a go
Plain Text
#!/bin/sh

# Set up environment variables
export KINDE_DOMAIN="your_kinde_subdomain"
export KINDE_CLIENT_ID="your_kinde_client_id"
export KINDE_CLIENT_SECRET="your_kinde_client_secret"
export APP_REDIRECT_URI="your_app_redirect_url"
export CALLBACK_AUTHORIZATION_CODE="CALLBACK_AUTHORIZATION_CODE"

# Make the POST request using curl
curl -X POST "https://${KINDE_DOMAIN}.kinde.com/oauth2/token" \
     -d client_id=${KINDE_CLIENT_ID} \
     -d client_secret=${KINDE_CLIENT_SECRET} \
     -d grant_type=authorization_code \
     -d redirect_uri=${APP_REDIRECT_URI} \
     -d code=${CALLBACK_AUTHORIZATION_CODE}
Maybe you could just shorten it to this:
Plain Text
curl -X POST "https://<your_kinde_subdomain>.kinde.com/oauth2/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d client_id=<your_kinde_client_id> \
     -d client_secret=<your_kinde_client_secret> \
     -d grant_type=authorization_code \
     -d redirect_uri=<your_app_redirect_url> \
     -d code=<CALLBACK_AUTHORIZATION_CODE>
even if it was just on another tab in that code block, it would've saved me over a day on setup
That screenshot is from here "Using Kinde without an SDK - Handling the callback": https://kinde.com/docs/developer-tools/using-kinde-without-an-sdk/#handling-the-callback
Add a reply
Sign up and join the conversation on Discord