---
updatedAt: 2026-08-04T15:36:51.000Z
agentTools:
  siteIndex: https://developers.pismo.io/llms.txt
  projectIndex: https://developers.pismo.io/pismo-docs/llms.txt
---

# Authentication with OpenID Connect

OpenID Connect (OIDC) for Servers is the preferred way to authenticate with the Pismo platform. It provides greater security guarantees in service communications than basic authentication using client credentials. OIDC authentication uses permission groups to control access to Pismo API endpoints. Through these permission groups, you can give targeted access to only a specific set of endpoints to different groups within your organization or to a third party. To request an access token, use the <Anchor label="Get_OpenID_access_token" target="_blank" href="ref:post-passport-v1-oauth2-token">Get\_OpenID\_access\_token</Anchor> endpoint.

# Server authentication

During onboarding to production, you must generate at least one pair of public and private keys for OpenID authentication. Then, you must generate and sign a **JSON Web Token** (JWT) for each key pair that you generate. For more information about JWTs, refer to <Anchor label="Generate your JWT" target="_blank" href="#generate-your-jwt">Generate your JWT</Anchor>.

1. Use the <Anchor label="OpenSSL" target="_blank" href="https://www.openssl.org/">OpenSSL</Anchor> toolkit to generate a private key and a public key, using the following commands:

```text Generating private and public keys
Generate a private key:
  $ openssl genpkey -out rsakey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
Generate a public key based on the private key you generated:
  $ openssl rsa -in rsakey.pem -out rsa.pub -pubout -outform PEM
```

2. Store your private key securely, and then create an OIDC authentication configuration in Control Center. For more information, refer to the Control Center <Anchor label="OpenID Connect" target="_blank" href="doc:openid-configurations">OpenID Connect</Anchor> guide.
3. Generate your JWT using the private key associated with the public key you shared, using the <Anchor label="claims values here" target="_blank" href="#generate-your-jwt">claims values here</Anchor>.
   <Callout icon="📘" theme="info">
     Note that if you are giving third-party access, the third party needs to be able to generate JWTs, as described in <Anchor label="Generate your JWT" target="_blank" href="doc:authentication-with-openid#generate-your-jwt">Generate your JWT</Anchor>. The JWT generated for this authentication must have a unique value for the `aud`claim. This is how the Pismo platform can tell that a request comes from a third party, not the direct client.
   </Callout>
4. Request an access token using the endpoint <Anchor label="Get OpenID access token" target="_blank" href="ref:post-passport-v1-oauth2-token-1">Get OpenID access token</Anchor>.
   <Callout icon="📘" theme="info">
     The response to this call includes an access token that you can use with an API endpoint. If the response does not include an access token, your JWT might not be properly formed.
   </Callout>
5. Retrieve the access token returned in the Pismo response.
6. Use this access token to make API calls.

The following diagram illustrates steps 3 to 6:

<Image align="center" alt="Diagram illustrating token requests and responses" border={true} src="https://files.readme.io/2676e0d-Authentication_with_OpenID_Connect_for_Servers_V6.png" className="border" />

# Generate your JWT

<a name="generate-your-jwt" />

Generate and sign a JWT using the private key you generated (in Step 1 above). This JWT should contain the following claims:

<Table align={["left","left","left","left","left"]}>
  <thead>
    <tr>
      <th>
        Claim (key)
      </th>

      <th>
        Type
      </th>

      <th>
        Mandatory
      </th>

      <th>
        Value
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        alg
      </td>

      <td>
        Fixed string value
      </td>

      <td>
        Yes
      </td>

      <td>
        RS256
      </td>

      <td>
        This is part of the header.
      </td>
    </tr>

    <tr>
      <td>
        typ
      </td>

      <td>
        Fixed string value
      </td>

      <td>
        Yes
      </td>

      <td>
        JWT
      </td>

      <td>
        This is part of the header.
      </td>
    </tr>

    <tr>
      <td>
        iss
      </td>

      <td>
        Fixed string value
      </td>

      <td>
        Yes
      </td>

      <td />

      <td>
        This is the verifier Issuer. This value is provided by Pismo, automatically generated by Control Center when a user creates a new credential.
      </td>
    </tr>

    <tr>
      <td>
        sub
      </td>

      <td>
        Fixed string value
      </td>

      <td>
        Yes
      </td>

      <td />

      <td>
        This is the verifier Subject. This value is provided by Pismo, automatically generated by Control Center when a user creates a new credential.
      </td>
    </tr>

    <tr>
      <td>
        aud
      </td>

      <td>
        Fixed string value
      </td>

      <td>
        Yes
      </td>

      <td />

      <td>
        This is the verifier Audience. This value is provided by Pismo, automatically generated by Control Center when a user creates a new credential.
      </td>
    </tr>

    <tr>
      <td>
        iat
      </td>

      <td>
        Timestamp number
      </td>

      <td>
        Yes
      </td>

      <td>
        ex: 1697044489
      </td>

      <td>
        The current time in seconds since the UNIX epoch.
      </td>
    </tr>

    <tr>
      <td>
        exp
      </td>

      <td>
        Timestamp number
      </td>

      <td>
        Yes
      </td>

      <td>
        ex: 1697048089
      </td>

      <td>
        The time in seconds since the UNIX epoch at which the token expires. It can be a maximum of 3600 seconds later than the `iat`.
      </td>
    </tr>

    <tr>
      <td>
        tenant\_id
      </td>

      <td>
        Fixed string value
      </td>

      <td>
        Yes
      </td>

      <td>
        ex: YcjCAADMVI-TN-e3adf01
      </td>

      <td>
        The unique tenant identifier of the user/token.

        Value provided by Pismo, automatically generated by Control Center when a new OpenID key is registered.

        This is not to be confused with the Org ID.
      </td>
    </tr>

    <tr>
      <td>
        uid
      </td>

      <td>
        String value
      </td>

      <td>
        Yes (refer to <Anchor label="Endpoints that require an account-specific token" target="_blank" href="ref:endpoints-that-require-an-account-specific-token">Endpoints that require an account-specific token</Anchor>)
      </td>

      <td>
        ex: "uid": "104753059"
      </td>

      <td>
        The Account ID of the Pismo customer.

        If you want a token without Account ID,  do not send the claim **uid** on the JWT.
      </td>
    </tr>

    <tr>
      <td>
        pismo
      </td>

      <td>
        Object type
      </td>

      <td>
        Yes
      </td>

      <td>
        ex:\
        "pismo": \{\
        "group": "pismo-v1:accounts:rw,pismo-v1:admin:rw"\
        },
      </td>

      <td>
        The identifier of the group that you want your token to have access to. A group has access to a certain subset of endpoints. You can add more than one group, separated by a comma.

        You can find the list of valid groups on Control Center under the section **Permission groups** in the main menu. Otherwise, contact your Pismo representative.

        For full details on API permission groups, refer to the <Anchor label="Permission groups" target="_blank" href="doc:cc-api-permission-groups">Permission groups</Anchor> guide.
      </td>
    </tr>

    <tr>
      <td>
        customClaims
      </td>

      <td>
        Object type
      </td>

      <td>
        No
      </td>

      <td>
        "customClaims": \{\
        "custom1": "someValue",\
        "userexample": "myusername"\
        },
      </td>

      <td>
        Optional custom claims.
      </td>
    </tr>
  </tbody>
</Table>

Example of the structure of the JWT that you should generate on your side in order to use OpenID authentication:

```Text JWT sample header
Header
{
  "alg": "RS256",
  "typ": "JWT"
}
```

```json JWT sample payload
Payload
{
  "aud": "<value provided by Pismo>",
  "iss": "<value provided by Pismo>",
  "sub": "<Value provided by Pismo>",
  "tenant_id": "<Value provided by Pismo>",
  "pismo": {
    "group": "pismo-v1:some-samplegroup:rw"
  },
  "uid": "123123",
  "exp": 1724348241,
  "iat": 1724344641,
  "customClaims": {
    "custom1": "someValue",
    "userexample": "user@user.com"
  }
}
```

<Callout icon="📘" theme="info">
  In the Payload example above, all values marked as "value provided by Pismo" are automatically generated in Control Center when a new OpenID is generated. You can find the values on the OpenID Connect details page.

  <Image align="center" alt="Screenshot of Control Center OpenID Connect details page" src="https://files.readme.io/c21cab544c25a9000f64f7d652d496fb408341ce2304dde36f640fc17a6a2f6f-OpenID-ControlCenter2.png" />
</Callout>

You can find the list of groups available for your organization in Control Center. For more information, refer to <Anchor label="Permission groups" target="_blank" href="doc:cc-api-permission-groups">Permission groups</Anchor>.

**uid** is required on all <Anchor label="endpoints that require an account-specific token" target="_blank" href="ref:endpoints-that-require-an-account-specific-token">endpoints that require an account-specific token</Anchor>.

To generate JWTs, we recommend you use one of the libraries provided at <Anchor label="jwt.io" target="_blank" href="https://jwt.io/">jwt.io</Anchor>. You can use any trusted helper library that is compatible with your stack.

The following code sample shows how to create a signed JWT in a few languages:

```go GoLang
package main

import (
    "fmt"
    "time"

    "github.com/dgrijalva/jwt-go"
)

var mySigningKey = []byte(`
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
`)

func main() {

    key, err := jwt.ParseRSAPrivateKeyFromPEM(mySigningKey)
    fmt.Println(err)

    token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
        "iss":       "test-123@test-7a65.iam.gserviceaccount.com",
        "aud":       "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
        "exp":       time.Now().Add(time.Hour).Unix(),
        "iat":       time.Now().Unix(),
        "sub":       "test-123@test-7a65.iam.gserviceaccount.com",
        "uid":       "12345678910",
        "tenant_id": "test-pbrj4",
    })

    // Sign and get the complete encoded token as a string using the secret
    tokenString, err := token.SignedString(key)
    fmt.Println(tokenString, err)

}
```

```java Java
package com.star.sud;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.*;

import io.jsonwebtoken.*;


public class JwtTokenwithRsa {

	//Reference to your private key
	public static final String PRIVATE_KEY_FILE_PATH = "RSA/rsakey.pem";

	public static void main(String[] args) {
		try {
			JwtTokenwithRsa tokenwithRsa = new JwtTokenwithRsa();
			tokenwithRsa.generateJWTWithRsa();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public String generateJwtToken(PrivateKey privateKey) {
		JwtBuilder builder = Jwts.builder().signWith(SignatureAlgorithm.RS256, privateKey);

		long issuedAt = Instant.now().getEpochSecond();
		long expiresAt = Instant.now().plusSeconds(3600).getEpochSecond();
		Map<String,String> pismoClaims = new HashMap<String,String>();
		pismoClaims.put("group","pismo-v1:samplegroup:rw");

		builder.claim("aud", "value provided by Pismo");
		builder.claim("sub", "value provided by Pismo");
		builder.claim("iss", "value provided by Pismo");
		builder.claim("exp", expiresAt);
		builder.claim("iat",issuedAt);
		builder.claim("pismo", pismoClaims);
		builder.claim("tenant_id", "Your Tenant ID");
		builder.claim("uid", "Account Id or Tenant Id");


		String token = builder.compact();
		return token;
	}

	public void generateJWTWithRsa() throws Exception {
		KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
		keyGenerator.initialize(1024);

		File f = new File(PRIVATE_KEY_FILE_PATH);
		PrivateKey privateKey = readPrivateKey(f);


		// Generate Token
		String id = UUID.randomUUID().toString();
		String token = generateJwtToken(privateKey);
		System.out.println("TOKEN:");
		System.out.println(token);

	}

	public RSAPrivateKey readPrivateKey(File file) throws Exception {
		String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

		String privateKeyPEM = key
				.replace("-----BEGIN PRIVATE KEY-----", "")
				.replaceAll(System.lineSeparator(), "")
				.replace("-----END PRIVATE KEY-----", "");

		byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);

		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
		return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
	}

}
```

```node Node
To be released
```

```python Python
import jwt
import time
# Read the private key from a file
with open('/Users/agentle/tools/pismo-jwt/rsakey.pem', 'r') as key_file:
    private_key = key_file.read()
if not private_key:
    raise ValueError("You must provide a private key to generate the JWT.")
# Generate the current time in seconds since the UNIX epoch
current_time = int(time.time())
# Define the expiration time (such as 1 hour from now)
expiration_time = current_time + 3600  # 3600 seconds = 1 hour
payload = {"iss":       "MitXVzVtmT-TN-e3adf01@dev-gke-a4019.iam.gserviceaccount.com",
           "aud":       "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
           "exp":       expiration_time,
           "iat":       current_time,
           "sub":       "MitXVzVtmT-TN-e3adf01@dev-gke-a4019.iam.gserviceaccount.com",
           "tenant_id": "MitXVzVtmT-TN-e3adf01",
           "pismo": {
                "group": "pismo-v1:accounts:rw,pismo-v1:admin:rw"
                    }
        }
# for an endpoint requiring account token here's an
# account ID "uid": "104753059"
token = jwt.encode(payload, private_key, algorithm='RS256')
print(token)
```

## Using the JWT to request an access token

After generating the signed JWT, you can use it to request an access token. This access token request is an HTTPS POST request.

Method: `POST`

Content-Type: `application/json`

Endpoint: `{environment}/passport/v1/oauth2/token`

Request body payload:

| Property Name | Type   | Description                                        |
| :------------ | :----- | :------------------------------------------------- |
| token         | string | A custom token you use to request an access token. |

The following sample code shows a cURL POST request for an access token in the Pismo Sandbox:

```curl
curl --request POST \
--url https://sandbox.pismolabs.io/passport/v1/oauth2/token \
--header 'Content-Type: application/json' \
--data '{
"token":"<your_bearer_token>"
}'
```

## Handling the response

If the JWT is properly formed and your service has permission to perform the operation, then the JSON response from the authorization server should include an access token. The following is an example response.

```json
{
  "access_token": "<your_bearer_token>",
  "refresh_token": "<your_refresh_token>",
  "expires_in": "3600"
}
```

> 📘 Access tokens expire after one hour
>
> Your application should catch the error generated when this happens. It can then generate another JWT, sign it, and request another access token.

# Calling Pismo APIs

After your application obtains an access token, you can use it to make calls to a Pismo API on behalf of the given user account. To do this, include the access token in the `authorization: Bearer` header  of each request, as example below.

```curl cURL on a service
curl --request POST \
  --url https://sandbox.pismolabs.io/service/v1/service/example/payment \
  --header 'Content-Type: application/json' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer <your_bearer_token>' \
  --data '{< JSON body the service expects to receive>}'

```

# Third-party authentication with OIDC

OIDC authentication uses permission groups to control access to Pismo API endpoints. Through these permission groups, you can give a third party access to only a specific set of endpoints within your organization. For more information, refer to the <Anchor label="Third-party authentication" target="_blank" href="doc:third-party-authentication-with-openid">Third-party authentication</Anchor> guide.

<br />