Skip to content

Support for authentication_openid_connect Plugin #1713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
colussim opened this issue Apr 28, 2025 · 1 comment
Closed

Support for authentication_openid_connect Plugin #1713

colussim opened this issue Apr 28, 2025 · 1 comment

Comments

@colussim
Copy link

Hi
I am currently using the go-sql-driver/mysql driver to connect to a MySQL database that requires OpenID Connect authentication. I am encountering an issue when attempting to use the authentication_openid_connect plugin with this driver.

The plugin is returning the following error during the client-server handshake (mysql Log) :

[ERROR] [MY-015153] [Server] Plugin authentication_openid_connect reported: 'An error occurred during the client server handshake.'

Additionally, the Go client (go-sql-driver/mysql) outputs:

[mysql] 2025/04/28 13:03:55 auth.go:341 unknown auth plugin:authentication_openid_connect_client

When I try to connect using the following DSN:

mysql_app@tcp(X.X.X.X:3306)/identity_demo?tls=custom&allowCleartextPasswords=1&auth_client_plugin=authentication_openid_connect&authentication_openid_connect_client_id_token_file=.%2Ftokens%2Ftoken_1745836161.txt

I have confirmed that the OIDC configuration and token generation are correct, as I am able to use the generated token successfully via curl. However, when attempting to use the token in my Go application with the go-sql-driver/mysql, the authentication fails with the error mentioned above.

Here is a summary of the steps I have taken:
1. The MySQL server has the authentication_openid_connect plugin properly configured.
2. I use the clientcredentials.Config structure from the oauth2 package to obtain an OIDC token.
3. I pass the token as the password in the MySQL connection string.
4. The error persists, even though the token works with curl and is valid.

Could you provide any guidance or recommendations for fixing this issue? Is there anything specific that might need to be done on the go-sql-driver/mysql side to properly handle the OIDC authentication handshake?

Driver version : v1.9.2
Go version: go1.23.6 darwin/arm64
Server version: MySQL Enterprise 9.2
Server OS: : container image on podman 5.2.3

Thank you in advance for your help!

@colussim
Copy link
Author

colussim commented May 8, 2025

Hi ,

I found a way to get JWT authentication working with the authentication_openid_connect plugin by making a few targeted changes to the go-sql-driver/mysql source code.

I’ve detailed everything below, including references to the MySQL protocol and the exact code changes I made. I hope this can be helpful, and I’d be happy to collaborate further if this is something you’d consider integrating upstream.

Here’s the complete breakdown:

Protocol Reference

According to the MySQL documentation,
the client must send an authentication packet with the following format for OpenID Connect:

  • 1 byte: Capability flag (currently always 0x01)
  • Length-encoded string: The JWT ID token

Packet structure:

[Capability flag][Length of Length][Length of ID token][ID token]

The length-encoded string format is described here:
https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_strings.html#sect_protocol_basic_dt_string_le


Summary of Required Modifications

1. DSN Parsing

  • Parse the DSN for OIDC parameters:
    • auth_client_plugin
    • authentication_openid_connect_client_id_token_file
  • Store them in the config for use during handshake.

2. packets.go: Add/Modify writeLengthEncodedString

// writeLengthEncodedString writes a string in MySQL's Length-Encoded String format.
func writeLengthEncodedString(buf *bytes.Buffer, b []byte) {
    n := len(b)
    switch {
    case n < 251:
        buf.WriteByte(byte(n))
    case n < 0x10000:
        buf.WriteByte(0xfc)
        buf.WriteByte(byte(n))
        buf.WriteByte(byte(n >> 8))
    case n < 0x1000000:
        buf.WriteByte(0xfd)
        buf.WriteByte(byte(n))
        buf.WriteByte(byte(n >> 8))
        buf.WriteByte(byte(n >> 16))
    default:
        buf.WriteByte(0xfe)
        for i := 0; i < 8; i++ {
            buf.WriteByte(byte(n >> (8 * i)))
        }
    }
    buf.Write(b)
}

3. packets.go: Modify writeHandshakeResponsePacket

if authPlugin == "authentication_openid_connect" || authPlugin == "authentication_openid_connect_client" {
    tokenFilePath, ok := mc.cfg.Params["authentication_openid_connect_client_id_token_file"]
    if !ok || tokenFilePath == "" {
        return fmt.Errorf("OIDC plugin selected but no JWT token file provided")
    }
    jwtBytes, err := os.ReadFile(tokenFilePath)
    if err != nil {
        return fmt.Errorf("failed to read JWT token file: %v", err)
    }
    jwtToken := strings.TrimSpace(string(jwtBytes))
    var buf bytes.Buffer
    buf.WriteByte(0x01) // Capability flag
    writeLengthEncodedString(&buf, []byte(jwtToken))
    authResp = buf.Bytes()
}

4. connection.go: Exclude OIDC Params from SQL

for param, val := range mc.cfg.Params {
    if param == "auth_client_plugin" || param == "authentication_openid_connect_client_id_token_file" {
        continue // skip OIDC params
    }
    // ...existing code...
}

5. auth.go: Support OIDC Plugin in Auth Logic

In auth():

case "authentication_openid_connect_client":
    token, ok := mc.cfg.Params["authentication_openid_connect_client_id_token_file"]
    if !ok || token == "" {
        return nil, fmt.Errorf("OIDC token not provided")
    }
    return []byte(token), nil

In handleAuthResult():

case "authentication_openid_connect":
    token, ok := mc.cfg.Params["authentication_openid_connect_client_id_token_file"]
    if !ok {
        return errors.New("missing required param 'authentication_openid_connect_client_id_token_file'")
    }
    var packet []byte
    packet = append(packet, byte(1)) // capability bit
    packet = appendLengthEncodedInteger(packet, uint64(len(token)))
    packet = append(packet, []byte(token)...)
    if err := mc.writePacket(packet); err != nil {
        return fmt.Errorf("failed to send OIDC token with capability: %w", err)
    }
    return mc.resultUnchanged().readResultOK()

Usage

To use JWT authentication with the MySQL OpenID Connect plugin in your Go application:

1. Generate or Obtain a JWT Token

Obtain a valid JWT token from your OIDC provider and save it to a file (for example: /tmp/mysql_token.txt).

2. Build the DSN

Add the following parameters to your MySQL DSN:

  • auth_client_plugin=authentication_openid_connect_client
  • authentication_openid_connect_client_id_token_file=/path/to/your/token.txt

Example DSN:

mysql_app@tcp(mysql.demos.com:3306)/identity_demo?tls=custom&allowCleartextPasswords=1&auth_client_plugin=authentication_openid_connect_client&authentication_openid_connect_client_id_token_file=/tmp/mysql_token.txt

3. Open the Database Connection

In your Go code, use the DSN as usual with sql.Open:

escapedTokenFilePath :=url.QueryEscape("/tmp/mysql_token.txt")
dsn := fmt.Sprintf("%s@tcp(%s)/%s?tls=custom&allowCleartextPasswords=1&auth_client_plugin=authentication_openid_connect_client&authentication_openid_connect_client_id_token_file=%s",
    "mysql_app",
    "mysql.demos.com:3306",
    "identity_demo",
    escapedTokenFilePath,
)
db, err := sql.Open("mysql", dsn)
if err != nil {
    log.Fatalf("Failed to connect: %v", err)
}
defer db.Close()

4. Authentication Flow

  • The driver will read the JWT token from the file specified by authentication_openid_connect_client_id_token_file.
  • The token will be sent to MySQL as part of the authentication handshake, following the OpenID Connect plugin protocol.

Rationale

  • The JWT token is securely read from a file at connection time.
  • The handshake and authentication packets are constructed according to the MySQL OIDC protocol.
  • OIDC-specific DSN parameters are not sent as SQL, but only used for authentication.

With these modifications, the Go MySQL driver can authenticate using a JWT token with the MySQL OpenID Connect plugin.

@colussim colussim closed this as completed May 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant