Skip to content

Token Endpoint Hands-on

The Direct Access Grant — also called the password grant — lets you exchange a username and password directly for tokens at the token endpoint, bypassing the browser redirect flow entirely.

This makes it convenient for:

  • Testing your Keycloak configuration from a terminal
  • CLI tools and shell scripts that need a token to call an API
  • Development workflows where you want to quickly inspect a token

It should not be used in real user-facing applications. When you hand a user’s password directly to your app, the app sees the credentials — which defeats the purpose of delegating authentication to Keycloak. For browser and mobile apps, always use Authorization Code + PKCE.

To use this grant, the client must have Direct access grants enabled on the Capability config tab.

Replace the placeholders with your values before running.

curl -X POST https://${KC_URL}/realms/${REALM}/protocol/openid-connect/token \
  -d "grant_type=password" \
  -d "client_id=${CLIENT_ID}" \
  -d "username=${USERNAME}" \
  -d "password=${PASSWORD}"

For a confidential client, also add -d "client_secret=<your-secret>" to the command.

A successful request returns a JSON object like this:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"session_state": "abc123",
"scope": "openid profile email"
}

Key fields:

  • access_token — a JWT you send to your API as Authorization: Bearer <token>.
  • expires_in — the access token lifetime in seconds (300 = 5 minutes by default).
  • refresh_token — use this to get a new access token without re-authenticating.
  • id_token — a JWT that proves who the user is (OIDC only).
  • token_type — always Bearer for Keycloak.

The access token is a Base64url-encoded JWT. You can decode the payload to inspect its claims.

echo ${ACCESS_TOKEN} | cut -d. -f2 | base64 -d | jq .

On some systems use base64 --decode instead of base64 -d. Set the ACCESS_TOKEN environment variable to your token value first, or replace it inline.

What is the Direct Access Grant (password grant) primarily used for?
What header do you use to send an access token to an API?
What does expires_in represent in the token response?
Why should the password grant be avoided in real apps?