Signed Identity is a more secure way to provide contact identifiers. When the user logs in on your website or mobile app, your backend server will return a signed payload containing the identifiers for this user. This signed payload is called SignedIdentity. Take a look at the following sequence diagram:
Identify Contact with Signed Identity
After the signed identity is retrieved, your web or mobile application can use it to identify the contact as follows:
Generate Signed Identity
The backend server can generate a signed identity for a user as follows:
Get the signing key and issuer from the application settings in the Bird dashboard (Developer > Applications > (your application) > Overview tab).
Sign the user identifiers payload using the signing key.
// Call backend server for user login
val response = userLogin()
// and get signed identity
val signedIdentity = response.signedIdentity
bird.contact.identify( SignedIdentity(signedIdentity) )
// Coming soon...
// Call your authenticated backend to get a signed identity
// Your backend must verify the user's session before issuing the JWT
const resp = await fetch('/api/bird-identity', {
method: 'POST',
credentials: 'include', // sends session cookies for authentication
});
const { signedIdentity } = await resp.json();
await Bird.contact.identify({
strategy: 'SignedIdentityClaims',
signedIdentity: signedIdentity,
});
onst crypto = require('crypto');
const express = require('express');
const app = express();
// These come from your Bird dashboard:
// Developer > Applications > (your app) > Overview > Identity Signing Key
const issuer = process.env.BIRD_ISSUER; // e.g. "mrn:v1:application:identity-claims-issuer:<workspaceId>/<appId>:1"
const signingKey = process.env.BIRD_SIGNING_KEY; // e.g. "d45013b0eb5355fe..."
function makeSignedIdentity(identifiers) {
let header = JSON.stringify({
"alg": "HS256",
"typ": "JWT",
"kid": issuer,
});
let payload = JSON.stringify({
identifiers: identifiers,
});
let headerBase64 = Buffer.from(header).toString('base64url');
let payloadBase64 = Buffer.from(payload).toString('base64url');
let signature = crypto
.createHmac('sha256', signingKey)
.update(headerBase64 + "." + payloadBase64)
.digest('base64url');
return headerBase64 + "." + payloadBase64 + "." + signature;
}
// Endpoint that your frontend calls to get a signed identity
app.post('/api/bird-identity', async (req, res) => {
// IMPORTANT: Authenticate the user BEFORE generating a signed identity.
// Use your existing session/auth system (cookies, JWT, OAuth, etc.)
// If this endpoint issues JWTs without authentication, any visitor
// could impersonate any contact.
const user = await getUserFromSession(req);
if (!user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Build identifiers from YOUR trusted user record.
// Do NOT use identifiers sent from the client request body.
let identifiers = [
{ key: "emailaddress", value: user.email },
];
let signedIdentity = makeSignedIdentity(identifiers);
res.json({ signedIdentity });
});
app.listen(3000);
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
// These come from your Bird dashboard:
// Developer > Applications > (your app) > Overview > Identity Signing Key
var issuer = os.Getenv("BIRD_ISSUER") // e.g. "mrn:v1:application:identity-claims-issuer:<workspaceId>/<appId>:1"
var signingKey = os.Getenv("BIRD_SIGNING_KEY") // e.g. "d45013b0eb5355fe..."
type Identifier struct {
Key string `json:"key"`
Value string `json:"value"`
}
func makeSignedIdentity(identifiers []Identifier) (string, error) {
header, _ := json.Marshal(map[string]string{
"alg": "HS256",
"typ": "JWT",
"kid": issuer,
})
payload, _ := json.Marshal(map[string]interface{}{
"identifiers": identifiers,
})
headerBase64 := base64.RawURLEncoding.EncodeToString(header)
payloadBase64 := base64.RawURLEncoding.EncodeToString(payload)
dataToSign := headerBase64 + "." + payloadBase64
h := hmac.New(sha256.New, []byte(signingKey))
h.Write([]byte(dataToSign))
signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
return fmt.Sprintf("%s.%s.%s", headerBase64, payloadBase64, signature), nil
}
// Endpoint that your frontend calls to get a signed identity
func birdIdentityHandler(w http.ResponseWriter, r *http.Request) {
// IMPORTANT: Authenticate the user BEFORE generating a signed identity.
// Use your existing session/auth system (cookies, JWT, OAuth, etc.)
// If this endpoint issues JWTs without authentication, any visitor
// could impersonate any contact.
user, err := getUserFromSession(r)
if err != nil || user == nil {
http.Error(w, `{"error":"Not authenticated"}`, http.StatusUnauthorized)
return
}
// Build identifiers from YOUR trusted user record.
// Do NOT use identifiers sent from the client request body.
identifiers := []Identifier{
{Key: "emailaddress", Value: user.Email},
}
signedIdentity, err := makeSignedIdentity(identifiers)
if err != nil {
http.Error(w, `{"error":"Failed to generate identity"}`, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"signedIdentity": signedIdentity,
})
}
func main() {
http.HandleFunc("/api/bird-identity", birdIdentityHandler)
http.ListenAndServe(":3000", nil)
}