Skip to main content

Method A — Client secret

When to use it

The client-secret method is the simplest way for a machine to authenticate: the service account holds a client id and a shared secret, and sends them as HTTP Basic auth on every token request. It's the right starting point for a quick proof of concept or a simple deployment where you can store a secret safely.

The trade-off is inherent to shared secrets: the secret crosses the wire on every request. Anyone who obtains it can impersonate the service account until you rotate it. Treat it like a password — store it in a secret manager, never in source control — and when your security posture demands that the secret never travels, move to private-key JWT or SPIFFE. See the comparison if you're unsure.

How it works

On the wire this is OAuth's client_credentials grant with client_secret_basic client authentication:

POST <token_endpoint>
Authorization: Basic base64(client_id ":" client_secret) ← ClientSecretAuth adds this
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&resource=<MCP_URL>&scope=<requested>


{ "access_token": "eyJ…", "expires_in": 3600, "token_type": "Bearer" }

The SDK discovers <token_endpoint> from the MCP server URL (PRM → AS metadata, see how a token is acquired), builds the Authorization: Basic header from your credentials, and caches the returned token. You supply only the id, the secret, and the MCP URL.

Step 1 — Create the service account

In the create dialog, name it, keep Client secret selected (the default), and click Create service account:

Create service account — client secret

Step 2 — Save the credentials (shown once)

Client secret credentials

Copy both values into your service's environment — the secret is shown once. The dialog also confirms the wire mechanics (client_credentials grant, client_secret_basic auth), which is exactly what the SDK performs for you:

AUTHSEC_ISSUER=https://mcpauthz.com
SA_CLIENT_ID=8a7c1107-...
SA_CLIENT_SECRET=<the secret you copied> # 64 hex chars — copy, don't retype
MCP_URL=https://your-mcp-server.example.com/mcp

Step 3 — Acquire a token

First grant the service account a role on the target application (a valid credential with no grant returns access_denied). Then:

package main

import (
"context"
"log"
"os"

authsec "github.com/authsec-ai/sdk-authsec/packages/go-sdk"
)

func main() {
agent := authsec.NewAgentIdentity(authsec.AgentIdentityConfig{
Issuer: os.Getenv("AUTHSEC_ISSUER"),
ClientID: os.Getenv("SA_CLIENT_ID"),
Auth: authsec.NewClientSecretAuth(os.Getenv("SA_CLIENT_SECRET")),
})

token, err := agent.AccessFor(context.Background(), os.Getenv("MCP_URL"),
authsec.WithRequestedScopes("test_mcp:read", "test_mcp:tools:read"))
if err != nil {
log.Fatalf("access_for: %v", err)
}
log.Printf("acquired token (%d chars) — send as Authorization: Bearer", len(token))
}

What this does, and what the SDK handles for you:

  • NewClientSecretAuth(secret) builds the client_secret_basic credential. It panics on an empty secret — an empty secret is always a configuration bug, so it fails immediately rather than producing confusing 401s later. The AgentIdentityConfig{ClientSecret: "..."} shorthand is identical on the wire; use whichever reads better (but never set both — that also panics).
  • AccessFor(ctx, MCP_URL, WithRequestedScopes(...)) runs the full flow: discovers the token endpoint from MCP_URL, adds the Basic header, requests client_credentials with resource=MCP_URL, and caches the token for reuse. Build the agent once and reuse it — it holds the cache.
  • You never construct the Basic header, call the token endpoint, or parse expires_in — that's all inside AccessFor.

Send the returned string as Authorization: Bearer <token> on your MCP calls, then confirm it works with the verify snippet.

Rotating the secret

Because the secret travels on every request, rotate it periodically and immediately if it may have leaked: generate a new secret in the dashboard (Service Account → rotate), deploy it to SA_CLIENT_SECRET, and the old one stops working. There's no keypair or JWKS to manage — the simplicity that makes this method easy is also why rotation is the only lever you have.


Shared steps for every method: grant access · verify · troubleshooting