OAuth 2.0 Asset Token Flow for Securing Connected Devices
To integrate IoT devices with the Salesforce API, use the OAuth 2.0 asset token flow. Asset tokens are an open-standards-based JWT authentication token for verifying and securing requests from connected devices. Asset tokens identify the device to a backend service that processes the stream of data and events from the device. These tokens allow registration of device data with the Salesforce platform and linking the device to Salesforce CRM data about the customer, account, or contact.
Required Editions
| Available in: both Salesforce Classic and Lightning Experience |
| Available in: All Editions |
See New connected apps can no longer be created in Spring ‘26 for more details.
Devices—via connected apps—use the OAuth 2.0 asset token flow to request an asset token from Salesforce. In this flow, an OAuth access token and an actor token are exchanged for an asset token. This flow combines asset token issuance and asset registration for efficient token exchange and automatic linking of devices to Service Cloud Asset data.
For example, your customer purchases a connected device and registers an account with your support site. Your company offers a mobile app that acts as an agent for device registration. A connected app allows the user to connect to the device, log in to your support site, and register the device. In exchange for an access token and an actor token, your site issues an asset token that identifies the device to your backend cloud service.
After registration, the device can operate independently of the connected app, routinely sending data about its state and operations to your backend service. If the backend proactively detects an abnormal behavior or state, it automatically creates a case. The case is associated with the asset, which is tied directly to the customer’s contact record and your company’s support process. The device can also signal that an office supply, such as ink toner, is low, potentially identifying a new market opportunity.
The OAuth 2.0 asset token flow involves these general steps.
- Configure a connected app to issue asset tokens for connected devices. See Enable OAuth Settings for API Integration.
- The connected app requests an access token from the Salesforce token endpoint.
- After Salesforce grants an access token, the connected app requests an asset token using the OAuth 2.0 token exchange protocol.
- If the asset token JWT is valid, Salesforce grants an asset token and registers the device.
- Salesforce publishes an asset token event.
Request an Access Token
For a connected app to issue an asset token for the connected device, it first invokes an OAuth 2.0 flow to request an access token. After receiving the access token, the connected app exchanges it for an asset token, which it requires for device registration.
Common methods for getting access tokens include the OAuth 2.0 web-server flow and the OAuth 2.0 JWT bearer flow. With a browser and a utility such as cURL, you can get and exchange an authorization code as follows.
- In a browser, go to this URL after you substitute the placeholder values. https://your_site_url/services/oauth2/authorize?response_type=code&client_id=your_client_id&redirect_uri=your_url_encoded_redirect_uri
- Log in and approve your app.
- When you receive a callback, get the authorization code. Replace it along with the other
placeholder values in exchange for an access
token.
curl -s -k -d "grant_type=authorization_code&code=authorization_code&redirect_uri= your_url_encoded_redirect_uri&client_id=your_client_id&client_secret=your_client_secret" https://your_site_url/services/oauth2/token
Request an AssetToken
After receiving an access token, the connected app posts an asset token request to the Salesforce token endpoint, using the OAuth 2.0 token exchange protocol.
Here’s a sample request posted to the Salesforce token endpoint using an unsigned actor token.
POST /services/oauth2/token HTTP/1.1
Host: customersite.my.site.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
subject_token=0DB00001iSr!AR0AQDhXv4xNCYPUwRa3_KyiZxNgNmrJU7QPJUrvBRM9x
3MLnlEbOOsuldLL.3mb.nML1h8G8zAc2zkUU_q.HEM6bY0kOJjDL&
actor_token_type=urn:ietf:params:oauth:token-type:jwt&
actor_token=eyJhbGciOiJub25lIiwidHlwIjoiSldU In0.eyJkaWQiOiI4NTc4OTliOS02OTk4LTQzZD
QtODQ4My0xOTRlODBkNzE4Y2MiLCJBc3NldFRva2VuQ3VzdG9tQXR0cmlidXRlX19jIjoiTXkgVmFsdWUiLC
JBc3NldCI6eyJOYW1lIjoiRGlzaHdhc2hlciIsIlNlcmlhbE51bWJlciI6IjEyMzQ1Njc4In19.
The request includes these parameters:
| Parameter | Description |
|---|---|
grant_type
|
Use these values for the grant type:
urn:ietf:params:oauth:grant-type:token-exchange. |
subject_token_type
|
Required. Value must be
urn:ietf:params:oauth:token-type:access_token. |
subject_token
|
Required. The subject token is a security token that represents the identity of the user for whom the request is being made. For asset tokens, the value is the access token. |
actor_token_type
|
Optional unless actor_token is specified. Value must be
urn:ietf:params:oauth:token-type:jwt. |
actor_token
|
Optional. The actor token, which is a security token, represents the identity of the user who is authorized to use the requested security token and act on behalf of the subject. For asset tokens, the value is a JWT that contains metadata about your new or existing asset. If your use case requires Proof of Possession, you can include a confirmation key and sign the actor token. Otherwise, it can be unsigned. |
The actor token payload is a JWT containing a set of claims about the device you’re registering. All claims are optional. If you don’t pass any of this metadata, Salesforce can still issue your asset token, but it can’t register the device at the same time.
This example actor token payload includes an asset claim.
{
"did": "2c4c73e7-edc5-77dd-011d-43562d21cb7e",
"Name": "My Asset Token",
"cnf": {
"jwk": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": "AJNGcu8nW6xq2l_dAgbJmSfHLGRn-vCuKWY-LAELw-Kerjaj5Dq3ZGW38HR4BmZksG3g4eA1RXn1hiZGI1Q 6Ei59QE_OZQx2zVSTb7-oIwRcDHEB1-RraYT3LJuh4JwUDVfEj3WgDnTjE5vD46l_CR5EXf4VL8uo8T40FkA
51AhT"
}
},
"Asset": {
"Name": "Asset 19730",
"SerialNumber": "9461094121",
"AccountId": "001D000000KtKgS",
"MyCustomAssetField__c": "Depreciated"
}
}
You can include these claims in the actor token payload.
| Claim | Description |
|---|---|
did
|
The ID of the device. If included, it’s saved and returned in the issued asset token. |
Name
|
Name of the asset token. |
cnf
|
Confirmation containing a device-specific RSA public key in JSON Web Key format, which is defined in the Proof-of-Possession Key Semantics for JSON Web Tokens specification. If included, the actor token must be signed with the private key corresponding to the public key. |
Asset
|
Asset JSON object containing any valid fields from the asset object, including custom fields. If included, it’s used to link to an existing asset by ID or serial number, or to create an asset with the specified name. |
exp
|
Expiration. If included, it’s used to validate that the JWT hasn’t expired. |
After constructing your actor token payload, create the actor token JWT. The format depends on whether the actor token is unsigned or signed. Unsigned actor tokens carry claims for registering the asset without a corresponding device public key. Signed actor tokens let you include a public key in the resulting asset token. The device uses the public key to prove possession of the private key, ensuring the token is used by the device it was issued to.
Here’s a sample unsigned actor token:
eyJhbGciOiAibm9uZSJ9.eyAiZGlkIiA6ICIzNDk2NDMzMS04YTZjLTRmODYtYjM0Zi0zMjI3ZWExZjU1MDk iLCAiTmFtZSIgOiAiMzQ5NjQzMzEtOGE2Yy0Zjg2LWIzNGYtMzIyN2VhMWY1NTA5IiwgIkFzc2V0IiA6IHs
gIk5hbWUiIDogIk15IENvbm5lY3RlZCBEZXZpY2UiLCAiU2VyaWFsTnVtYmVyIiA6ICIzNDk2NDMzMS04YTZ jLTRmODYtYjM0Zi0zMjI3ZWExZjU1MDkiLCAiQWNjb3VudElkIiA6ICIwMDFEMDAwMDAwS3Uwd0UiIH0gfQ.Which decodes into a header of:
{"alg": "none"}
And a payload of:
{
"did": "34964331-8a6c-4f86-b34f-3227ea1f5509",
"Name": "My Asset Token",
"Asset" : {
"Name" : "My Connected Device",
"SerialNumber" : "349643 1-8a6c-4f86-b34f-3227ea1f5509",
"AccountId" : "001D000000Ku0wE"
}
}
There’s no signature block.
An unsigned actor token has a header with a single alg claim. The
algorithm claim indicates that no signature is applied to the JWT. The value must be
none.
The signature block is an RSA SHA256 signature of header.payload. that
is base64url encoded and appended to the JWT. This signature on the JWT must be verifiable
using the public key provided in the cnf claim. If valid, the public key
is saved to the asset token event record and included in the cnf claim in
the returned asset token JWT.
Here’s a sample signed actor token:
eyJhbGciOiAibm9uZSJ9.eyAiZGlkIiA6ICIzNDk2NDMzMS04YTZjLTRmODYtYjM0Zi0zMjI3ZWExZjU1MDk iLCAiTmFtZSIgOiAiMzQ5NjQzMzEtOGE2Yy0Zjg2LWIzNGYtMzIyN2VhMWY1NTA5IiwgIkFzc2V0IiA6IHs gIk5hbWUiIDogIk15IENvbm5lY3RlZCBEZXZpY2UiLCAiU2VyaWFsTnVtYmVyIiA6ICIzNDk2NDMzMS04YTZ
jLTRmODYtYjM0Zi0zMjI3ZWExZjU1MDkiLCAiQWNjb3VudElkIiA6ICIwMDFEMDAwMDAwS3Uwd0UiIH0gfQ. DLVM9EGvZ1VasMSzPbi7mviIXucvTktfOTYq62cTVduSsLKR4gX3Q8xfm85SfqoMaCQVXLNGkZ1iYv5LjJ4_ e69yl9r5gIML7qHDwQOSqgsNYd8HPK4qpXv1QMzBCRXCNenqQ0YhJbokCPWeHWWnPNGFYuquKL2fTTmczadToThe actor token decodes into this header:
{
"typ": "JWT",
"alg": "RS256"
}
The actor token also decodes into this payload:
{
"cnf": {
"jwk": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": "AJNGcu8nW6xq2l_dAgbJmSfHLGRn-vCuKWY-LAELw-Kerjaj5Dq3ZGW38HR4BmZksG3g4eA1RXn1hiZGI1Q 6Ei59QE_OZQx2zVSTb7-oIwRcDHEB1-RraYT3LJuh4JwUDVfEj3WgDnTjE5vD46l_CR5EXf4VL8uo8T40FkA
51AhT"
}
},
"did": "34964331-8a6c-4f86-b34f-3227ea1f5509",
"Name": "My Asset Token"
}
A signed actor token has a header with these claims:
| Claim | Description |
|---|---|
alg
|
Algorithm claim to identify the signature algorithm. Value must be
RS256. |
typ
|
Type claim identifies the type of token. Value must be
JWT. |
Salesforce Grants an Asset Token
If the actor token JWT is valid, the access token response from Salesforce contains a success message and returns your asset token. For example:
HTTP/1.1 200 OK
Date: Wed, 06 Jan 2017 21:25:11 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Cache-Control: no-cache, no-store,s-maxage=0 Content-Type: application/json;charset=UTF-8
{
"access_token":"eyJraWQiOiJBc3NldHMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOl siaHR0cDovL2xvY2FsaG9zdDo1MDAwIl0sIm5iZiI6MTQ1MjExNTUxMSwiaXNzIjoiaHR0cHM6Ly9hc3NldG lkLWRldmVsb3Blci1lZGl0aW9uLm5hMS1ibGl0ejAzLnNvbWEuZm9yY2UuY29tIiwiaWQiOiIwNWJEMDAwMD AwMDAwMUiLCJleHAiOjE0NTI2MzM5MTEsImFpZCI6IjAyaUQwMDAwMDAxNkdESyIsImRpZCI6IjM0OTY0Mz MxLThNmMtNGY4Ni1iMzRmLTMyMjdlYTFmNTUwOSJ9.poVKl-fBrFi9tgKurAM2vbyGWZ5asbXJ5nQlfA CsStFnksKiS14gD9_oK9RchHsLlAt3gSCkIXtdFMmHDPQECtfpvhXhCBw-FAYLNnhJVivU7oNimiUzEVhYlw
-p7V3Qr2lmPzhXzEisELWuQgkyfABdiI9PUOjJA3rLb6RSBmCSA5sjkYKlCjnxRcMA9ZExgS5134yq_bPR V9BTrDwy2O34Ml2VwGjl9kIMWa5v8CHEtP7eMHbJpqgHQXpwnPna0ND4kAr7EQGF1zuIUBJMS35ZyZYtw5Eg da1QbYm8TGpRaKP7tiB0GL1_i3MhGeEC8qnlXg6pWuhJd_Q",
"issued_token_type":"urn:ietf:params:oauth:token-type:jwt",
"token_type":"Bearer",
"expires_in":51840
}
access_token parameter contains your issued asset token. The
expires_in parameter represents the validity length that you specified in
your connected app.The asset token JWT returned within the access token response contains a header, payload, and, if signed, a signature.
Here’s a sample signed asset token:
eyJraWQiOiJBc3NldHMiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOlsiaHR0cDovL2x
vY2FsaG9zdDo1MDAwIl0sIm5iZiI6MTQ1MjUzNzUyMCwiaXNzIjoiaHR0cHM6Ly9hc3NldGlkLWRldmV
sb3Blci1lZGl0aW9uLm5hMS1ibGl0ejAzLnNvbWEuZm9yY2UuY29tIiwiY25mIjp7Imp3ayI6eyJrdHk
iOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6ImRldmljZWtleSIsIm4iOiJoUHBlM1RqYkJTMHB2dzFoMGQ
zbWxqdkNsOVp4Uk9YNzhRNEhLUGVPQjBPWml3QzBaSXBtaHdWZGRQeWFxYklVdTJvcUJORkc3S2Q4YVM
zMDJtQVlkLXNuQzdIcnlzekd1YzIwOFIyMUVMTGJwZ2dWOUlKdl9zdnRMNXh4UjlUdjBfUFRBNFUyR0h
VbnE1dFdDTlNqVEhoYkNzWHNrSmlLSWN4MHR3N2tJMmNaR01VQTJvakEyb1U1YkFrSDYyUkNBUTNEcDl
xTktoNldxZlI3VUpyQlV6NTFuaHVEcnE5eTFRWE1RX3pNZENXLTVTUEFMTE3ekRJa2oyX3FSdmR6LUJQ
SFRPYkU4WXg3cGFlenYzS0hZa3prRHd1M3hqUGhEZGtXMURXMWFraC1zdVpQcFl3STl4bDJ6RUNJMVU1
UVdmT0szeTIwTW1jSEFtRXhtQXIyRHcifX0sImlkIjoiMDViRDAwMDAwMDAwMDVCIiwiZXhwIjoxNDUz
MDU1OTE3LCJhaWQiOiIwMmlEMDAwMDAwMTZHTWciLCJkaWQiOiIyYzRjNzNlNy1lZGM1LTc3ZGQtMDEx
ZC00MzU2MmQyMWNiN2UifQ.Bi-CJGeOUPibvw73oZN26fNM4wEjX1XF657s9dzAgJZCXZlFps4Atu-4H
eILBnnkEkGJPLhSLm88nWcANPIFVNQKkAEifQtUjj2QE7AIWdzoFC9RuyxFv0HnAwRYkJuqoE5en3HV9
8qWMxh1-J3m0eFRTS1tUPSnrKnnPvHktuH4TdRpi3RTl3bueXmgUdYeIXNcpG71wRZDheEGxK_p5Uejq
1YmZVz1a6TBAZG3bH5sJhO0ygk1eHlHwzc5Q0yEH7cI_T5wv7puu_TTiGpDFiWwOcnvsBA8Kf8-LWEaP
Ku6Aypgbg3Ii3kY6RQEQaeTYpd8Q_jSlJi7IwQIA5WbLAThe asset token decodes into this header:
{
"kid": "00D300000000mlxEAA.978",
"typ": "JWT",
"alg": "RS256"
}
The asset token also decodes into this payload:
{
"aud": ["https://your.devicebackend.com"],
"sub": "005WS000001c17CMJP",
"nbf": 1452537520,
"iss": "https://yoursite.com"
"cnf": {
"jwk": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": "hPpe3TjbBS0pvw1h0d3mljvCl9ZxROX78Q4HKPeOB0OZiwC0ZIpmhwVddPyaqbIUu2oqBNFG7Kd8aS302mA Yd-snC7HryszGuc208R21ELlbpggV9IJv_svtL5xxR9Tv0_PTA4U2GHUnq5tWCNSjTHhbCsXskJiKIcx0tw7 kI2cZGMUA2ojA2oU5bAkH62RCAQ3Dp9qNKh6WqfYwUJrBUz51nhuDrq9y1QXMQ_zMdCW-5SPALM17zDIkj2_ qRvdz-BPHTObE8Yx7paezv3KHYkzkDwu3xjPhDdkW1DW1akh-suZPpYwI9xl2zECI1U5QWfOK3y20MmcHAmE xmAr2Dw"
}
},
"id": "05bD0000000005B",
"exp": "1453055917"
"aid": "02iD00000016GMg",
"did": "2c4c73e7-edc5-77dd-011d-43562d21cb7e"
"custom_attributes": {
"customattribute1": "unfiltered",
}
}
The asset token header contains these claims.
| Claim | Description |
|---|---|
kid
|
Key ID claim identifies the org-specific public key associated with your connected app. It’s used to verify the signature block in a signed actor token. |
alg
|
Algorithm claim to identify the signature algorithm. Value must be
RS256. |
typ
|
Identifies the type of token. Value must be JWT. |
The asset token JWT payload contains these claims.
| Claim | Description |
|---|---|
iss
|
Issuer claim identifies the JWT issuer, which can be an Experience Cloud site URL, a My Domain login URL, or a custom domain URL. |
aud
|
Audience claim identifies who the JWT is intended for. Value is an array of
case-sensitive strings, each containing a StringOrURI value. An
audience is specified for each intended consumer of the asset token. |
sub
|
Subject claim identifies the 18-character, case-insensitive ID of the current user of the JWT. |
exp
|
Expiration time claim identifies the time when the JWT can no longer be processed. Value must be a numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds. |
nbf
|
Not before claim identifies the time before the JWT can be processed. The
processing of the nbf claim requires that the current date/time
must be after or equal to the not-before date/time listed in the
nbf claim. Value must be a numeric value representing the number
of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring
leap seconds. |
did
|
Device ID claim represents the ID for your device. If the actor token included
a did claim, it’s included in the asset token. |
aid
|
Asset ID claim represents the ID of the new or existing asset that the access token is linked to. |
cnf
|
Confirmation claim contains a device-specific RSA key in JSON Web Key format, as defined in the Proof-of-Possession Key Semantics for JSON Web Tokens specification. It’s used to verify Proof of Possession of the device’s private key. |
| Additional claims | Additional claims can include custom attributes or custom permissions that you specified in your connected app. |
Salesforce attempts to link the asset token to an existing asset or to create an asset, using this decision model.
- If the asset claim contains an ID claim, Salesforce attempts to link to an existing asset with a matching ID.
- If the asset claim contains a serial number claim, Salesforce attempts to link to an existing asset with a matching serial number.
- If the asset claim contains a name claim, Salesforce creates (registers) an
asset.
Note Creating an asset requires an asset AccountId or ContactId. - Otherwise, Salesforce doesn’t link to or create an asset. You can separately link an asset later, via the API.
Salesforce Publishes an Asset Token Event
If the actor token and access token are exchanged for an asset token, Salesforce publishes an asset token event. For more information about the AssetTokenEvent object and an example of how to trigger an action after an asset token event, see AssetTokenEvent.

