zitadel / oidc

Easy to use OpenID Connect client and server library written for Go and certified by the OpenID Foundation
https://zitadel.com
Apache License 2.0
1.37k stars 145 forks source link

Does Zitadel support generating access_token using client_secret_jwt for the grant type of client_credentials #655

Open idavollen opened 4 weeks ago

idavollen commented 4 weeks ago

Preflight Checklist

Describe the docs your are missing or that are wrong

I can find neither documentation nor code example of testing _clientcredentials with _client_secretjwt to generate access token for machine-to-machine communication without sending the client secret to the authorization server.

I ran https://github.com/zitadel/oidc/blob/3b64e792ed1c01daf6bb3320a8da4ffa346753c2/example/server/main.go as OP and created a Go client that attempts to create access_token with client_secret_jwt.

But in the end, I've got this error message: Error getting access token: failed to get access token: {"error":"invalid_client"}

I'm looking forward to your confirmation or shed light on how to use Zitadel OIDC for my requested purpose.


package main

import (
    "bytes"
    "crypto/tls"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "time"

    "github.com/golang-jwt/jwt/v4"
)

// Function to generate a client_secret_jwt
func generateClientSecretJWT(clientID, clientSecret, tokenURL string) (string, error) {
    // Define the claims for the JWT
    claims := jwt.MapClaims{
        "iss": clientID,
        "sub": clientID,
        "aud": tokenURL,
        "exp": time.Now().Add(time.Minute * 5).Unix(), // 5 minutes expiration
        "iat": time.Now().Unix(),
        "jti": "unique-jwt-id", // You can generate this dynamically
    }

    // Create a new JWT token with HS256 signing method
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

    // Sign the token using the client secret
    signedToken, err := token.SignedString([]byte(clientSecret))
    if err != nil {
        return "", err
    }

    return signedToken, nil
}

// Function to request an access token from the Authorization Server
func requestAccessToken(clientID, clientSecret, tokenURL string) (string, error) {
    // Generate the client_secret_jwt
    clientAssertion, err := generateClientSecretJWT(clientID, clientSecret, tokenURL)
    if err != nil {
        return "", err
    }
    fmt.Println("Generated jwt payload: ", clientAssertion)

    // Prepare the form data for the token request
    data := url.Values{}
    data.Set("grant_type", "client_credentials")
    data.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
    data.Set("client_assertion", clientAssertion)

    // Create a new POST request
    req, err := http.NewRequest("POST", tokenURL, bytes.NewBufferString(data.Encode()))
    if err != nil {
        return "", err
    }
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    // Send the request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

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

    if resp.StatusCode != http.StatusOK {
        return "", fmt.Errorf("failed to get access token: %s", string(body))
    }

    // Parse the JSON response to extract the access token
    var result map[string]interface{}
    if err := json.Unmarshal(body, &result); err != nil {
        return "", err
    }

    accessToken, ok := result["access_token"].(string)
    if !ok {
        return "", fmt.Errorf("access token not found in response")
    }

    return accessToken, nil
}

func main() {
// I also tried api/secret without luck
    clientID := "sid1"
    clientSecret := "everysecret"
    tokenURL := "https://www.myexample.com/oauth/token"
    // Request the access token
    accessToken, err := requestAccessToken(clientID, clientSecret, tokenURL)
    if err != nil {
        fmt.Println("Error getting access token:", err)
        return
    }

    fmt.Println("Access Token:", accessToken)
}

Additional Context

No response

idavollen commented 2 weeks ago

any progress? Can you confirm that the client_secret_jwt is for the time being not supported? The more complicated _private_keyjwt is already supported, why is the easier one, _client_secretjwt is not supported?