# developer Security - Full Documentation # About This Guide This guide walks you through an end-to-end Headless Identity implementation, from completing your Salesforce setup to calling Headless Identity API. The example implementation in this guide is designed for a single-page app or public client. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| The guide takes you through these high-level tasks. - Complete prerequisites for Headless Identity. - Create an authentication provider. - Configure Experience Cloud settings. - Create an external client app. - Use Postman to call Headless Identity APIs. The guide also includes some JavaScript examples for implementing Headless Identity with a single-page app. To use this guide, you must have a Salesforce admin account. Complete these steps in a non-production environment, such as a sandbox or developer org. Use this guide as a starting point to customize and deploy your own solution in production. # About This Guide Restriction rules are a record-level access control mechanism that allows you to grant granular visibility to specified users. This guide outlines the different use cases of restriction rules and describes how to set them up using the Tooling or Metadata API. Restriction rules are available in Lightning Experience in Enterprise, Performance, Unlimited, and Developer editions. Before creating restriction rules, turn off Salesforce Classic for your org. Find instructions at [Turn Off Salesforce Classic for Your Org](https://help.salesforce.com/s/articleView?id=xcloud.lex_enable_users_disable_switcher.htm&type=5). Set up restriction rules in Salesforce by navigating to a supported object in the Object Manager. You can provide feedback and suggestions for restriction rules in the [Restriction Rules](https://trailblazers.salesforce.com/_ui/core/chatter/groups/GroupProfilePage?g=0F94S000000Gwle) group in the Trailblazer Community. - **[Restriction Rules](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-about.md)** Restriction rules let you enhance your security by allowing certain users to access only specified records. They can prevent users from accessing records that can contain sensitive data or information that isn’t essential to their work. Restriction rules are available for custom objects, external objects, contracts, events, quotes, tasks, time sheets, and time sheet entries and can be configured in the Object Manager or through the Tooling or Metadata API. - **[Quick Start: Control Which Tasks the Sales Team Can Access](https://developer.salesforce.com/docs/platform/restriction-rules/quickstart/restriction-rules-quickstart.md)** In this Quick Start, we create a restriction rule that controls which tasks members of the Sales Team can access. - **[Considerations](https://developer.salesforce.com/docs/platform/restriction-rules/restriction-rules-considerations.md)** Keep these considerations and limitations in mind while using restriction rules. - **[Example Scenarios](https://developer.salesforce.com/docs/platform/restriction-rules/examples/restriction-rules-examples.md)** Refer to these sample restriction rules that fulfill different access requirements. To review how these example scenarios are built in Salesforce, see Restriction Rule Example Scenarios in Salesforce Help. - **[Tooling API Reference](https://developer.salesforce.com/docs/platform/restriction-rules/restriction-rules-reference-tooling.md)** This section provides more information on the RestrictionRule Tooling API object used to create restriction rules. - **[Metadata API Reference](https://developer.salesforce.com/docs/platform/restriction-rules/restriction-rules-reference-metadata.md)** This section provides more information on the RestrictionRule Metadata API type used to create restriction rules. **See Also** - [*Salesforce Help*: Restriction Rules](https://help.salesforce.com/s/articleView?id=platform.security_restriction_rule.htm&type=5) # Access Grants When an object has its organization-wide default set to Private or Public Read Only, Salesforce uses access grants to define how much access a user or group has to that object’s records. Each access grant gives a specific user or group access to a specific record. It also records the type of sharing tool — sharing rule, team, etc. — used to provide that access. Salesforce uses four types of access grants: explicit grants, group membership grants, inherited grants, and implicit grants. - Explicit Grants - : Salesforce uses explicit grants when records are shared directly to users or groups. Specifically, Salesforce uses explicit grants when: - A user or a queue becomes the owner of a record. - A sharing rule shares the record to a personal or public group, a queue, a role, or a territory. - An assignment rule shares the record to a user or a queue. - A territory assignment rule shares the record to a territory. - A user manually shares the record to a user, a personal or public group, a queue, a role, or a territory. - A user becomes part of a team for an account, opportunity, or case. - A programmatic customization shares the record to a user, a personal or public group, a queue, a role, or a territory. **note:** If your organization doesn’t have an efficient sharing architecture, it might encounter performance problems when you use automated processes that generate a very large number of explicit grants, such as major sales realignments. - Group Membership Grants - : Grants that occur when a user, personal or public group, queue, role, or territory is a member of a group that has explicit access to the record. For example, if a sharing rule explicitly grants the Strategy group access to the Acme record, and Bob is a member of the Strategy group, Bob’s membership in the Strategy group grants him access to the Acme record. - Inherited Grants - : Grants that occur when a user, personal or public group, queue, role, or territory inherits access through a role or territory hierarchy, or is a member of a group that inherits access through a group hierarchy. - Implicit Grants - : Grants that occur when non-configurable record-sharing behaviors built into Salesforce Sales, Service, and Portal applications grant access to certain parent and child records. For example, with this default logic, sometimes referred to as _built-in sharing_, users can view a parent account record if they have access to its child opportunity, case, or contact record. If those users have access to a parent account record, they can also access its child opportunity, case, and contact records. # Activate Your Site To complete your Experience Cloud site setup for headless identity, activate your site. This important but often overlooked step is required to expose Headless Identity API endpoints. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From Administration workspaces, click **Settings**. 2. Click **Activate**. 3. Click **OK** to accept the warning. You receive an email when the site’s activated. Your Experience Cloud site is now fully prepared for headless identity. # Allow Users to See an External Object's Records This restriction rule allows active Salesforce users to see the records of an external object called Purchase Order. The rule uses a field called IsClosed on Purchase Order records in its record criteria. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| **note:** Only external objects created using the Salesforce Connect: OData 2.0, OData 4.0, and Cross-Org adapters support restriction rules. Find out more in Restriction Rule Considerations. ## Tooling API ```nolang { "FullName":"purchase_orders_restictionrule", "Metadata": { "active":true, "description":"Allows accounting department users to access open purchase order records from external system.", "enforcementType":"Restrict", "masterLabel":"OpenPurchaseOrderRecords", "recordFilter":"IsClosed__c = ‘false’", "targetEntity":"PurchaseOrder__x", "userCriteria":"$User.Department = ‘Accounting’", "version":1 } } ``` ## Metadata API ```nolang true Allows accounting department users to access open purchase order records from external system. Restrict OpenPurchaseOrderRecords IsClosed__c = ‘false’ PurchaseOrder__x $User.Department = ‘Accounting’ 1 ``` **See Also** - [*Salesforce Help*: Restriction Rule Considerations](https://help.salesforce.com/s/articleView?id=platform.security_restriction_rule_considerations.htm&type=5) - [*Salesforce Help*: Define External Objects](https://help.salesforce.com/s/articleView?id=platform.external_object_define.htm&type=5) # Allow Users to See Only Records Owned by Same Profile This restriction rule allows active users to see only the events owned by users that have the same profile. You can use dot notation to traverse object and field relationships in the `recordFilter` field. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| ## Tooling API ```nolang { "FullName":"restictionruleeventsprofile", "Metadata": { "active":true, "description":"Allows active users to see only events owned by users of the same profile.", "enforcementType":"Restrict", "masterLabel":"Events Owned by Same Profile", "recordFilter":"Owner:User.ProfileId = $User.ProfileId", "targetEntity":"Event", "userCriteria":"$User.IsActive = true", "version":1 } } ``` ## Metadata API ```nolang true Allows active users to see only events owned by users of the same profile. Restrict Events Owned by Same Profile Owner:User.ProfileId = $User.ProfileId Event $User.IsActive = true 1 ``` # Allow Users to See Only Records Owned by Same Role This restriction rule allows active users to see only the events owned by users that have the same role. You can use dot notation to traverse object and field relationships in the `recordFilter` field. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| ## Tooling API ```nolang { "FullName":"restictionruleeventsrole", "Metadata": { "active":true, "description":"Allows active users to see only events owned by users of the same role.", "enforcementType":"Restrict", "masterLabel":"Events Owned by Same Role", "recordFilter": "Owner:User.UserRoleId = $User.UserRoleId", "targetEntity":"Event", "userCriteria":"$User.IsActive = true", "version":1 } } ``` ## Metadata API ```nolang true Allows active users to see only events owned by users of the same role. Restrict Events Owned by Same Role Owner:User.UserRoleId = $User.UserRoleId Event $User.IsActive = true 1 ``` # Allow Users to See Only Records That They Own This restriction rule allows users with the designated profile to see only the tasks that they own. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| ## Tooling API ```nolang { "FullName":"restriction_rule_tasks_you_own", "Metadata": { "active":true, "description":"Allows users of a specific profile to see only tasks that they own.", "enforcementType":"Restrict", "masterLabel":"Tasks You Own", "recordFilter":"OwnerId = $User.Id", "targetEntity":"Task", "userCriteria":"$User.ProfileId = '00exxxxxxxxxxxx'", "version":1 } } ``` ## Metadata API ```nolang true Allows users with a specific profile to see only tasks that they own. Restrict Tasks You Own OwnerId = $User.Id Task $User.ProfileId = '00exxxxxxxxxxxx' 1 ``` # Allow Users to See Only Specified Record Type This restriction rule allows the designated users to see only contracts that have a specified record type. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| ## Tooling API ```nolang { "FullName":"Contract restriction rule on RecordType", "Metadata": { "active":true, "description":"View Contracts with RecordType = Internal.", "enforcementType":"Restrict", "masterLabel":"RR for Internal Contracts", "recordFilter":"RecordTypeId = '012xxxxxxxxxxxx'", "targetEntity":"Contract", "userCriteria":"$User.UserRoleId = '00Exxxxxxxxxxxx'", "version":1 } } ``` ## Metadata API ```nolang true View Contracts with RecordType = Internal. Restrict RR for Internal Contracts RecordTypeId = '012xxxxxxxxxxxx' Contract $User.UserRoleId = '00Exxxxxxxxxxxx' 1 ``` # Allow Users to See Records Based on a Custom Field This restriction rule allows high-volume users to see only the contracts where the user's department matches the contract's department. This rule uses a custom field, `Department__c`, that must have the appropriate value set through Apex, Process Builder, workflows, or flows. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| ## Tooling API ```nolang { "FullName":"Contract restriction rule for Customer Community", "Metadata": { "active":true, "description":"Show high-volume user by department", "enforcementType":"Restrict", "masterLabel":"RR for Internal Contracts", "recordFilter":"Department__c = $User.Department", "targetEntity":"Contract", "userCriteria":"$User.UserType = 'CSPLitePortal'", "version":1 } } ``` ## Metadata API ```nolang true Show high-volume user by department Restrict RR for Internal Contracts Department__c = $User.Department Contract $User.UserType = 'CSPLitePortal' 1 ``` # Assign the Headless Identity Admin Role to Yourself Assign the role that you created to your System Administrator user. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| |User Permissions Needed| | |-----------------------|--| |To view roles and role hierarchy:|View Roles and Role Hierarchy| |To create, edit, and delete roles:|Manage Roles| |To assign users to roles:|Manage Internal Users| 1. From your role detail page, select **Assign Users to Role**. If you left this page, you can get back to it by entering `Roles` in the Quick Find box, selecting **Roles**, and then selecting the role that you created. 2. If you don’t see your admin user in Available Users, use the dropdown and search bar to refine your search. 3. Select your admin user, and add them to Selected Users for End Users. 4. Save your changes. # Attach Applied Discount Database Table This example outlines how to attach a database table that’s in the same Amazon RDS instance as the example solution. Suppose you want to attach your `MyAppliedDiscount` table to apply a discount for one or more order items. ## Update the Schema Add the type and input declarations for the `MyAppliedDiscount` table to the GraphQL schema. ``` input AWSDateOperator { eq: AWSDate gt: AWSDate ge: AWSDate in: [AWSDate] like: AWSDate lt: AWSDate le: AWSDate ne: AWSDate nin: [AWSDate] } input AWSDateTimeOperator { eq: AWSDateTime gt: AWSDateTime ge: AWSDateTime in: [AWSDateTime] like: AWSDateTime lt: AWSDateTime le: AWSDateTime ne: AWSDateTime nin: [AWSDateTime] } input AWSJSONOperator { eq: AWSJSON gt: AWSJSON ge: AWSJSON in: [AWSJSON] like: AWSJSON lt: AWSJSON le: AWSJSON ne: AWSJSON nin: [AWSJSON] } input AWSTimeOperator { eq: AWSTime gt: AWSTime ge: AWSTime in: [AWSTime] like: AWSTime lt: AWSTime le: AWSTime ne: AWSTime nin: [AWSTime] } input BooleanOperator { eq: Boolean gt: Boolean ge: Boolean in: [Boolean] like: Boolean lt: Boolean le: Boolean ne: Boolean nin: [Boolean] } enum Direction { ASC DESC } enum Enumeration { VALUE1 VALUE2 VALUE3 VALUE4 VALUE5 VALUE6 VALUE7 VALUE8 VALUE9 } input FloatOperator { eq: Float gt: Float ge: Float in: [Float] like: Float lt: Float le: Float ne: Float nin: [Float] } input IDOperator { eq: ID gt: ID ge: ID in: [ID] like: ID lt: ID le: ID ne: ID nin: [ID] } input IntOperator { eq: Int gt: Int ge: Int in: [Int] like: Int lt: Int le: Int ne: Int nin: [Int] } type Mutation { create_MyOrder(input: MyOrder_CreateInput!): MyOrder create_MyOrderItem(input: MyOrderItem_CreateInput!): MyOrderItem create_MyProduct(input: MyProduct_CreateInput!): MyProduct create_MyAppliedDiscount(input: MyAppliedDiscount_CreateInput!): MyAppliedDiscount delete_MyOrder(id: ID!): MyOrder delete_MyOrderItem(id: ID!): MyOrderItem delete_MyProduct(id: ID!): MyProduct delete_MyAppliedDiscount(id: ID!): MyAppliedDiscount update_MyOrder(input: MyOrder_UpdateInput!): MyOrder update_MyOrderItem(input: MyOrderItem_UpdateInput!): MyOrderItem update_MyProduct(input: MyProduct_UpdateInput!): MyProduct update_MyAppliedDiscount(input: MyAppliedDiscount_UpdateInput!): MyAppliedDiscount } type MyAppliedDiscount implements Node { id: ID! DiscountId: ID! OrderItemOrderId: String OrderItemProductId: String Multiplier: Float } type MyAppliedDiscount_Connection { edges: [MyAppliedDiscount_Edge] pageInfo: PageInfo! } input MyAppliedDiscount_CreateInput { DiscountId: ID! OrderItemOrderId: String OrderItemProductId: String Multiplier: Float } type MyAppliedDiscount_Edge { cursor: String! node: MyAppliedDiscount } input MyAppliedDiscount_FilterInput { and: [MyAppliedDiscount_FilterInput] not: MyAppliedDiscount_FilterInput or: [MyAppliedDiscount_FilterInput] id: IDOperator DiscountId: StringOperator OrderItemOrderId: StringOperator OrderItemProductId: StringOperator Multiplier: FloatOperator } input MyAppliedDiscount_OrderByInput { id: OrderByClause DiscountId: OrderByClause OrderItemOrderId: OrderByClause OrderItemProductId: OrderByClause Multiplier: OrderByClause } input MyAppliedDiscount_UpdateInput { id: ID! DiscountId: ID! OrderItemOrderId: String OrderItemProductId: String Multiplier: Float } type MyOrder implements Node { id: ID! OrderId: ID! OrderDate: AWSDateTime Status: String TotalCost: Float } type MyOrderItem implements Node { id: ID! ParentOrderId: ID! ParentProductId: ID! Quantity: Int } type MyOrderItem_Connection { edges: [MyOrderItem_Edge] pageInfo: PageInfo! } input MyOrderItem_CreateInput { ParentOrderId: ID ParentProductId: ID Quantity: Int } type MyOrderItem_Edge { cursor: String! node: MyOrderItem } input MyOrderItem_FilterInput { and: [MyOrderItem_FilterInput] not: MyOrderItem_FilterInput or: [MyOrderItem_FilterInput] id: IDOperator ParentOrderId: StringOperator ParentProductId: StringOperator Quantity: IntOperator } input MyOrderItem_OrderByInput { id: OrderByClause ParentOrderId: OrderByClause ParentProductId: OrderByClause Quantity: OrderByClause } input MyOrderItem_UpdateInput { id: ID! ParentOrderId: ID ParentProductId: ID Quantity: Int } type MyOrder_Connection { edges: [MyOrder_Edge] pageInfo: PageInfo! } input MyOrder_CreateInput { OrderDate: AWSDateTime OrderId: ID! Status: String TotalCost: Float } type MyOrder_Edge { cursor: String! node: MyOrder } input MyOrder_FilterInput { and: [MyOrder_FilterInput] not: MyOrder_FilterInput or: [MyOrder_FilterInput] id: IDOperator OrderDate: AWSDateTimeOperator OrderId: StringOperator Status: StringOperator TotalCost: FloatOperator } input MyOrder_OrderByInput { id: OrderByClause OrderId: OrderByClause OrderDate: OrderByClause Status: OrderByClause TotalCost: OrderByClause } input MyOrder_UpdateInput { id: ID! OrderDate: AWSDateTime OrderId: String Status: String TotalCost: Float } type MyProduct implements Node { id: ID! ProductId: ID! Name: String Price: Float Discontinued: Boolean } type MyProduct_Connection { edges: [MyProduct_Edge] pageInfo: PageInfo! } input MyProduct_CreateInput { ProductId: ID! Name: String Price: Float Discontinued: Boolean } type MyProduct_Edge { cursor: String! node: MyProduct } input MyProduct_FilterInput { and: [MyProduct_FilterInput] not: MyProduct_FilterInput or: [MyProduct_FilterInput] id: IDOperator ProductId: StringOperator Name: StringOperator Price: FloatOperator Discontinued: BooleanOperator } input MyProduct_OrderByInput { id: OrderByClause ProductId: OrderByClause Name: OrderByClause Price: OrderByClause Discontinued: OrderByClause } input MyProduct_UpdateInput { id: ID! ProductId: String Name: String Price: Float Discontinued: Boolean } interface Node { id: ID! } enum NullsOrder { NULLS_FIRST NULLS_LAST } input OrderByClause { direction: Direction nulls: NullsOrder } type PageInfo { endCursor: String hasNextPage: Boolean! } type Query { myOrder( limit: Int, offset: Int, orderBy: [MyOrder_OrderByInput], where: MyOrder_FilterInput, first: Int, after: String ): MyOrder_Connection myOrderItem( limit: Int, offset: Int, orderBy: [MyOrderItem_OrderByInput], where: MyOrderItem_FilterInput, first: Int, after: String ): MyOrderItem_Connection myProduct( limit: Int, offset: Int, orderBy: [MyProduct_OrderByInput], where: MyProduct_FilterInput, first: Int, after: String ): MyProduct_Connection myAppliedDiscount( limit: Int, offset: Int, orderBy: [MyAppliedDiscount_OrderByInput], where: MyAppliedDiscount_FilterInput, first: Int, after: String ): MyAppliedDiscount_Connection node(id: ID!): Node } input StringOperator { eq: String gt: String ge: String in: [String] like: String lt: String le: String ne: String nin: [String] } ``` ## Update the Table Metadata Add metadata entries in the Parameter Store for the `MyAppliedDiscount` table. ``` **/appsync/typemetadata/MyOrder** { "secretName": "graphqlsample", "secretRegion": "us-west-2", "keyFields": [ "OrderId" ], "databaseTableName": "graphqlsample.my_order", "graphQLFields": { "OrderId": { "fieldType": "String", "columnName": "order_id" }, "OrderDate": { "fieldType": "AWSDateTime", "columnName": "order_date" }, "Status": { "fieldType": "String", "columnName": "status" }, "TotalCost": { "fieldType": "Float", "columnName": "total_cost" } } } **/appsync/typemetadata/MyOrderItem** { "secretName": "graphqlsample", "secretRegion": "us-west-2", "keyFields": [ "ParentOrderId", "ParentProductId" ], "databaseTableName": "graphqlsample.my_order_item", "graphQLFields": { "ParentOrderId": { "fieldType": "String", "columnName": "parent_order_id" }, "ParentProductId": { "fieldType": "String", "columnName": "parent_product_id" }, "Quantity": { "fieldType": "Int", "columnName": "quantity" } } } **/appsync/typemetadata/MyProduct** { "secretName": "graphqlsample", "secretRegion": "us-west-2", "keyFields": ["ProductId"], "databaseTableName": "graphqlsample.my_product", "graphQLFields": { "ProductId": { "fieldType": "String", "columnName": "product_id" }, "Name": { "fieldType": "String", "columnName": "name" }, "Price": { "fieldType": "Float", "columnName": "price" }, "Discontinued": { "fieldType": "Boolean", "columnName": "discontinued" } } } **/appsync/typemetadata/MyAppliedDiscount** { "secretName": "graphqlsample", "secretRegion": "us-west-2", "keyFields": [ "DiscountId", "OrderItemOrderId", "OrderItemProductId" ], "databaseTableName": "postgres.my_applied_discount", "graphQLFields": { "DiscountId": { "fieldType": "String", "columnName": "discount_id" }, "OrderItemOrderId": { "fieldType": "String", "columnName": "order_item_order_id" }, "OrderItemProductId": { "fieldType": "String", "columnName": "order_item_product_id" }, "Multiplier": { "fieldType": "Float", "columnName": "multiplier" } } } ``` ## Generate Table Qualifiers ### MyOrder This is a sample table qualifier generated for an external ID based on the id field of the MyOrder table. ``` { "columns": { "id": { "values": [ { "definition": "\"MyOrder-\"&SUBSTITUTE(ExternalId, \"-\", \"\\\\-\")" } ] }, "ExternalId": { "virtual": true, "values": [ { "definition": "SUBSTITUTE(RIGHT(id, LEN(id)-8), \"\\\\-\", \"-\")" } ] } } } ``` ### MyOrderItem This is a sample table qualifier generated for an external ID based on the id field of the MyOrderItem table. ``` { "columns": { "id": { "values": [ { "definition": "\"MyOrderItem-\"&SUBSTITUTE(SUBSTITUTE(ExternalId, \"-\", \"\\\\-\"), \"\\\\-PRD\", \"-PRD\")" } ] }, "ExternalId": { "virtual": true, "values": [ { "definition": "SUBSTITUTE(RIGHT(id, LEN(id)-12), \"\\\\-\", \"-\")" } ] } } } ``` ### MyProduct This is a sample table qualifier generated for an external ID based on the id field of the MyProduct table. ``` { "columns": { "id": { "values": [ { "definition": "\"MyProduct-\"&SUBSTITUTE(ExternalId, \"-\", \"\\\\-\")" } ] }, "ExternalId": { "virtual": true, "values": [ { "definition": "SUBSTITUTE(RIGHT(id, LEN(id)-10), \"\\\\-\", \"-\")" } ] } } } ``` ### MyAppliedDiscount This is a sample table qualifier for a virtual external ID defined using the `values` formula. ``` { "columns": { "id": { "values": [ { "definition": "\"MyAppliedDiscount-\"&SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(ExternalId, \"-\", \"\\\\-\"), \"\\\\-ORD\", \"-ORD\"), \"\\\\-PRD\", \"-PRD\")" } ] }, "ExternalId": { "virtual": true, "values": [ { "definition": "SUBSTITUTE(RIGHT(id, LEN(id)-18), \"\\\\-\", \"-\")" } ] }, "OrderItemId": { "virtual": true, "values": [ { "definition": "OrderItemOrderId&\"-\"&OrderItemProductId" } ] }, "OrderItemOrderId": { "values": [ { "definition": "LEFT(OrderItemId, FIND(\"-PRD\", OrderItemId)-1)" } ] }, "OrderItemProductId": { "values": [ { "definition": "RIGHT(OrderItemId, LEN(OrderItemId)-FIND(\"-PRD\",OrderItemId))" } ] } } } ``` # Attach Your Database Tables to the AppSync GraphQL API After you implement the example solution outlined here, you can explore how to surface your database tables to Amazon AppSync and Salesforce. If your database tables are in the same Amazon RDS instance as the example solution, complete these AWS configuration steps. See [Attach Applied Discount Database Table](/docs/platform/sf-connect-amazon-rds/references/sf-connect-amazon-rds-reference/attach-discount.html). 1. Update the schema. 1. From the [Amazon AppSync console](https://console.aws.amazon.com/appsync/), go to the **Schema** page. 2. In the editor, add the `type` and `input` declarations for the database table. To get the syntax correct, follow the [Graphqlsample_MyOrder](https://github.com/aws-quickstart/quickstart-salesforce-connect-appsync-rds-postgresql/blob/main/samples/postgres-schema.graphql) pattern. 3. Choose **Save Schema**. 2. Attach the resolver to the query and mutations for the new database table. 1. In the navigation pane of the Amazon AppSync Console, choose **Schema**. 2. In the Resolvers section, choose the query or mutation, and then click **Attach**. 3. In **Create new resolver**, choose the Lambda function from the dropdown list. Repeat the process to attach the resolver for all the queries and mutations defined in the GraphQL schema. For example, if the Salesforce Connect adapter for GraphQL can perform create, read, update, and delete operations on records, attach the resolver four times. 3. Add additional entries in the Parameter Store to capture the table metadata. 1. Open the [AWS Systems Manager console](https://console.aws.amazon.com/systems-manager/). 2. In the navigation pane, choose **Parameter Store**. 3. Follow the [Graphqlsample_MyOrder example](https://github.com/aws-quickstart/quickstart-salesforce-connect-appsync-rds-postgresql/blob/main/samples/postgres-schema.graphql) and create an additional parameter for the database table, including the `fieldTypes` and `keyColumns`. If your database tables are in a different Amazon RDS instance, you must complete additional configuration steps. Add the Amazon RDS credentials to the Secrets Manager, and set up the port forwarding so that the resolver can have a persistent connection to the RDS. If necessary, contact AWS Support for help with setting up this configuration. After the new GraphQL type is added to the API endpoint and validated, complete these Salesforce configuration steps. 1. Edit your external data source definition. 2. Sync the metadata so that Salesforce Connect can sync the new objects and fields required for the new database table. 3. Decide where you want to surface this external data in the Salesforce UI, and make the required updates. # Authorization and Access Control Authorization and access control ensures that your users can only access resources they’re permitted to use in a web application. Common vulnerabilities include directory traversal, insecure direct object reference, bypassing authorization mechanisms, and privilege escalation. ## Understand User Roles and Access Control User roles help in managing access rights and other privileges for your web applications. - Unauthenticated users can access only the login page. - Regular users can use most parts of the application but can’t access areas such as maintenance or setup pages. - Administrators have full access, including to the restricted areas of the application. Sometimes users can gain unauthorized access, called an authorization bypass. A specific type of authorization bypass, called privilege escalation, happens when a user with limited access gains permissions for a higher-level role, like an administrator. This authorization bypass can result in access to sensitive or restricted features. The way these vulnerabilities appear in a web application can be application-specific, but common authorization vulnerabilities exist and can be tested for [Authorization and access control](http://cwe.mitre.org/data/definitions/285.html) and are cited in the [CWE/SANS Top 25 Most Dangerous Programming Errors](http://cwe.mitre.org/top25/) report. - **[Test Your Application for Vulnerabilities](https://developer.salesforce.com/docs/platform/secure-coding/guide/secure-coding-access-control-test-for-vulnerabilities.md)** Test your applications for authorization and access control vulnerabilities to ensure web application security. - **[Protect Your Application from CRUD/FLS Vulnerabilities](https://developer.salesforce.com/docs/platform/secure-coding/guide/secure-coding-access-control-protect-from-crud-fls-vulnerabilities.md)** Implement secure coding practices in Apex and Visualforce by enforcing object-level, field-level, and record-level access controls to prevent unauthorized data exposure or manipulation. Follow your organization's security policy on both standard and custom objects while ensuring that your app adapts to users with limited access. - **[Protect Your Application from Sharing Violations](https://developer.salesforce.com/docs/platform/secure-coding/guide/secure-coding-access-control-protect-from-sharing-violations.md)** Enforce sharing rules to prevent inadvertent exposure of sensitive data. # Before You Start Before you create the restriction rule, make sure you have the needed permissions and tools. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage restriction rules:|Manage Sharing| |To view restriction rules:|View Setup & Configuration AND View Restriction and Scoping Rules| Before creating restriction rules, we recommend that you [Turn Off Salesforce Classic for Your Org](https://help.salesforce.com/s/articleView?id=xcloud.lex_enable_users_disable_switcher.htm&type=5). Salesforce can't guarantee that restriction rules work as intended for end users who are in the Salesforce Classic experience. You can use either the [Tooling](https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_api_tooling.htm) or [Metadata](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_intro.htm) API to create, retrieve, update, and delete restriction rules. In this Quick Start, we show you how to use both. **See Also** - [*Salesforce Developers Blog*: Explore the Salesforce APIs with a Postman Collection](https://developer.salesforce.com/blogs/2020/03/explore-the-salesforce-apis-with-a-postman-collection.html) # Before You Start Before you create a scoping rule, make sure you that have the needed permissions and tools. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance**,**Unlimited**, and **Developer** editions.| |User Permissions Needed| | |-----------------------|--| |To create and manage scoping rules:|Manage Sharing| |To view scoping rules:|View Setup & Configuration AND View Restriction and Scoping Rules| Creating scoping rules requires you to be comfortable using an API development application, such as [Postman](https://www.postman.com). You can use either the [Tooling](https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/intro_api_tooling.htm) or [Metadata](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_intro.htm) API to create, retrieve, update, and delete scoping rules. In this Quick Start, we show you how to use both. For help with creating scoping rules in Setup, see [Create a Scoping Rule](https://help.salesforce.com/s/articleView?id=platform.security_scoping_rule_create.htm&type=5). # Building a Native Single Sign-On Experience JavaScript Examples Use these high-level examples to understand how to create a native single sign-on (SSO) experience for a single-page app. This configuration uses a redirect-based flow to make it seem like your app natively integrates with an SSO provider. It isn’t technically headless, but the user experience is the same as the headless flows. ## Constructing an Authorization URL A key part of this configuration is the `sso-provider` parameter. You use this parameter to identify the SSO provider configured in Salesforce, whether it’s an authentication provider or a SAML identity provider. During the redirect-based flow, Salesforce checks for this parameter and redirects to the SSO provider so the user can log in. To use the `sso-provider` parameter with a redirect-based flow, you must first construct an authorization URL. This example constructs the URL and redirects the browser to the Experience Cloud site. ```nolang //Setup the Authorization URL redirectURL = expDomain + _authorizationURI; //Add Params to the Authorization URL redirectURL = redirectURL + '?client_id=' + _this.clientId; redirectURL = redirectURL + '&redirect_uri=' + _this.ssoCallbackURL; redirectURL = redirectURL + '&state=' + storeState(state); redirectURL = redirectURL + '&response_type=code'; //Specificy the SSO Provider redirectURL = redirectURL + '&sso_provider=' + ssoProviderDevName; //Add Scopes if (scopes!= null) { redirectURL = redirectURL + '&scopes=' + scopes; } //Add Code Challenge requestBody = requestBody + "&code_challenge=" + generateCodeChallenge(); //Redirect the Browser window.location.href = redirectURL; ``` The browser is redirected to the Experience Cloud site briefly, so the user never sees the Experience Cloud login page. The browser then automatically redirects to the SSO provider and loads the provider’s login page. The user logs in with their credentials from the provider. The browser is again briefly redirected to Salesforce before being automatically redirected to your app. ## Token Exchange The app must process the redirect to get the authorization code. ```nolang // Get URL Params from the callback URL queryString = window.location.search; urlParams = new URLSearchParams(queryString); console.log('Loading Callback Params: ' + urlParams); //Create the Code Response from the URL params codeResponse = new Object; codeResponse.code = urlParams.get('code'); codeResponse.state = urlParams.get('state'); codeResponse.sfdc_community_url = urlParams.get('sfdc_community_url'); // Call the common token exhcange method. tokenExchange(codeResponse, getCodeChallenge(), authorizationType.SSOLogin, null); ``` In the last line, this example calls a token exchange function. Here’s an example of this function. ```nolang function tokenExchange(response, codeChallenge, authorizeType, uniqueVisitorId) { // Get Values from Code Response code = response.code; stateIdentifier = response.state; baseURL = response.sfdc_community_url; state = null; // validate state if it was present if (stateIdentifier != null) { state = getState(stateIdentifier, true); if (state == null) { onError("A state param was sent back but no state was found"); return; } } // Create Client client = new XMLHttpRequest(); client.open("POST", expDomain + tokenURI, true); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // Build Request Body requestBody = "code=" + code + "&grant_type=authorization_code&client_id=" + _this.clientId + "&redirect_uri=" + _this.callbackURL; // Add PKCE requestBody = requestBody + "&code_verifier=" + generateCodeVerifier(); // Send Request client.send(requestBody); client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { //Access Tokens have been returned console.log("Code and Credntial Flow, token response: "); console.log(JSON.parse(client.response)); } else { onError("An error occured during token exchange for " + authorizeType, client.response) } } } } ``` # Building Native Single Sign-On Experiences You can configure the OAuth 2.0 web-server and user-agent flows to create a native single sign-on (SSO) experience in your app. Use this feature to add SSO to your Headless Identity implementation. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| To create a native single sign-on experience in your app, you configure SSO with an authentication provider or SAML identity provider. You add this provider as a login option from your Experience Cloud settings. Then you configure the OAuth 2.0 web server flow or user-agent flow with your app. You pass in a parameter that specifies the name of the SSO provider you configured. On your app, you also build a button to display the option to log in with the SSO provider. During the flow, when the user clicks the option to log in with the provider, the browser is redirected to your Experience Cloud site. Here, Salesforce checks for the SSO provider parameter specified in the authorization flow. The browser is then automatically redirected to the SSO provider. The user enters their credentials and the flow briefly redirects to your Experience Cloud site again before the user is redirected back to your app. These redirects happen automatically, giving the user the impression that your app is natively integrated with the external SSO provider—they don’t see or interact with your Experience Cloud site. For example, you want to set up SSO between your app and Google, and you want it to feel like your app is natively integrated with Google. Here’s a simplified overview of the flow works when it’s configured. ![Sequence diagram showing SSO with a headless app](https://developer.salesforce.com/media/headless-identity-impl-guide/headless_sso_diagram.png) - A user goes to your app and clicks a button so that they can log in with an external provider, such as Google (1). - The browser briefly redirects to the Experience Cloud login page. - Salesforce confirms that a Google SSO provider is enabled for the Experience Cloud site (2). - The browser automatically redirects to the Google login page. - The user enters their Google credentials (3). - Google authenticates the user. The browser is briefly redirected back to the Experience Cloud login page. - Salesforce finishes logging in the user (4). - The browser is automatically redirected back to your app. - The user is now logged in (5). # Complete Prerequisites for Headless Identity Cross off some basic setup steps in Salesforce and Google. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| In Salesforce, these prerequisites include creating a role to manage your users, setting up a demo profile, enabling Cross-Origin Resource Sharing (CORS), and setting up an account to contain your users. You must also enable the Authorization Code and Credentials Flow at an org-wide level so that you can set up headless login and registration. You must also implement Google reCAPTCHA to get a reCAPTCHA token so that you can use in the Postman examples. For single-page apps, with reCAPTCHA, you can secure your flow without passing secret information, like an integration user’s access token, in any of your requests to Salesforce. - **[Create a Role to Manage Headless Identity Features](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-create-role.md)** For identity and access management, it’s important to define who can access what. Create a role to ensure that you have the right level of access to manage Headless Identity features. - **[Assign the Headless Identity Admin Role to Yourself](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-assign-role.md)** Assign the role that you created to your System Administrator user. - **[Set Up a Demo Profile for End Users](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-create-profile.md)** For Headless Identity, you use profiles to define how your end users access data in Salesforce. New users are automatically assigned to this profile when they register. Create a demo profile so that you can test your headless login, registration, forgot password, and single sign-on processes as an end user. - **[Set Up Cross-Origin Resource Sharing (CORS)](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-cors.md)** In production, to use Headless Identity features in a web app, you must enable Cross-Origin Resource Sharing (CORS) so that your app can communicate with Salesforce. This step isn’t necessary for the example implementation in this guide, so you can skip it for now. But if you want to learn how to set up CORS, here’s how it’s done. - **[Create an Account for End Users](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-create-account.md)** You must use an account to store information about your end users, including their contact records. For Headless Identity, you reference the account in your Apex registration handlers. When new users log in via an authentication provider or sign up directly through your site, they’re added as contacts. - **[Enable the Authorization Code and Credentials Flow](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-enable-auth-code-creds-flow.md)** The Authorization Code and Credentials Flow is the foundation of headless login and headless registration. Enable this flow at an org-wide level. - **[Implement reCAPTCHA on a Web App](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-recaptcha.md)** For the Headless Registration Flow and the Headless Forgot Password Flow, you must configure at least one of two security settings on the Experience Cloud Login & Registration page. These settings add requirements to the requests that your app sends to Headless Identity APIs. You can require your app to send an access token issued to an internal integration user. Or you can require your app to send a reCAPTCHA token. For single-page apps, because you can’t keep the integration user’s access token private in the browser, we recommend that you require your app to send a reCAPTCHA token instead. Requiring reCAPTCHA helps you filter out invalid requests, such as requests from bots. To work with the Headless Identity API Postman collection, you must have a valid reCAPTCHA token. # Configure a Google Authentication Provider For this example, configure a Google authentication provider so that users can log in with Google. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| Authentication providers require you to use an app on the external identity provider to communicate with Salesforce. You can either set up an app yourself or use the default global app provided by Salesforce. In production, we recommend that you create your own app. For testing and for this example, you can use the global app, so you’re only required to fill in a few fields. Salesforce handles the rest. 1. From Setup, in the Quick Find box, enter `Auth`, and then select **Auth. Providers**. 2. Click **New**. 3. From the Provider Type dropdown, select **Google**. 4. For the name, enter `Google IDP`. 5. Ensure that the URL suffix is `Google_IDP`. 6. For Registration Handler, click ![Lookup icon](https://developer.salesforce.com/media/headless-identity-impl-guide/lookup.png), and then search for the name of the registration handler Apex class that you created. 7. For Execute Registration As, click ![Lookup icon](https://developer.salesforce.com/media/headless-identity-impl-guide/lookup.png), and then search for a user to run the registration handler Apex class. This user must have the Manage Users permission. For this example, you can use your admin user. 8. Leave the rest of the values blank, and save your changes. In production, when you set up your own app on the third-party SSO provider, you must fill in these fields. # Configure Experience Cloud Site Membership Because your Experience Cloud site stores all your user information, you must add your end-user profile to the site’s membership. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From Experience Workspaces, select **Administration**. If you’re not in Experience Workspaces, here’s how you can get back to it. In the Quick Find box, enter `Sites`, select **All Sites**, and then next to your site name, select **Workspaces**. 2. From the Administration page, select **Members**. 3. Search for the Headless Demo Profile that you created earlier, and add it to Selected Profiles. You can use the Search dropdown to filter profile types. 4. Save your changes. # Configure External Client App Policies In a standard OAuth flow, users often see an approval page where they confirm that an external client app is allowed to access their Salesforce data. With headless identity flows, you don’t want to show users a Salesforce approval page. To preapprove access, configure OAuth policies on your external client app. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. Go to your external client app policy page. 1. From Setup, in the Quick Find box, enter `App`, and then select **App Manager**. 2. Next to your app, click ![External client app edit dropdown arrow](https://developer.salesforce.com/media/headless-identity-impl-guide/connectedappdropdown.png), and then select **Edit Policies**. 2. Click **Edit Policies**. 3. Under OAuth Policies, set the Permitted Users policy to **Admin approved users are pre-authorized**. 4. Save the policy change. 5. On the external client app policy page, scroll down to and select **Manage Profiles**. 6. Select the headless demo profile that you created. 7. Save the policy change. # Configure Headless Identity Settings in Experience Cloud You control enablement, access, and security for the Headless Registration Flow and the Headless Forgot Password Flow on the Experience Cloud Login & Registration page. For this example, configure settings to support headless identity for a single-page app. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| ![Experience Cloud Login & Registration page](https://developer.salesforce.com/media/headless-identity-impl-guide/login_and_registration_page.png) The Login & Registration page is also where you configure identity features for users who interact with your Experience Cloud site directly. Most of the settings on this page, including settings for login, logout, password, and registration pages, affect the identity experiences for your site users. The settings in the Headless Identity Configuration section are separate and affect how your app calls Headless Identity APIs via your site. Headless Identity Configuration settings don’t affect how users interact with your Experience Cloud site. In general, only the settings in Headless Identity Configuration are relevant for setting up your implementation. There’s one exception, though. To create a native single sign-on (SSO) experience in your app, you must add the SSO provider to the Experience Cloud login page. During the flow, the browser is briefly redirected to the login page URL so that Salesforce can check to see if the SSO provider is enabled. The browser is then redirected to the provider. The redirection happens so quickly that the user never sees the Experience Cloud login page. The experience feels like headless SSO, even though it technically isn’t. 1. Go to the Login & Registration page. From Setup, in the Quick Find box, enter `Sites`, and then select **All Sites**. Next to your site name, click **Workspaces**, select **Administration**, and then select **Login & Registration**. 2. Add your SSO provider to the Experience Cloud login page. Under Login Page Setup, for login options, enable the Google IDP authentication provider you set up. 3. Enable headless registration. 1. Select **Allow self-registration via the Headless Registration API**. Enabling this setting exposes other settings related to headless registration. 2. Select **Require reCAPTCHA to access this API**, and leave **Require authentication to access this API** deselected. These settings control whether you need extra information—either a reCAPTCHA token or an access token— in your initial POST request to Headless Registration API. When you configure headless registration, you must require either authentication or reCAPTCHA—you can’t save your settings without at least one of these settings turned on. For this example, which is focused on single-page apps, requiring authentication isn’t recommended. A single-page app submits the registration POST request via the browser, and it can’t keep an access token safe. So requiring reCAPTCHA is the way to go. 3. For Default Profile, select the Headless Demo profile you set up. This profile gets assigned to new users automatically. 4. For Registration Handler, click ![Lookup icon](https://developer.salesforce.com/media/headless-identity-impl-guide/lookup.png), and then select your headless registration handler class. 5. For Run As, click ![Lookup icon](https://developer.salesforce.com/media/headless-identity-impl-guide/lookup.png), and then select your admin user. This user runs the headless registration handler. They must be able to access the account that contains your end users, and they must be assigned to the Headless Identity Admin role that you created. For this example, you can select yourself as the Run As user to make testing easier. In production, select a user that isn’t tied to a real person. That way, you don’t experience service disruptions if someone leaves the company and their account is disabled. 4. Enable headless password reset. 1. Select **Allow password reset via the Headless Forgot Password API**. 2. Select **Require reCAPTCHA to access this API**, and leave **Require authentication to access this API** deselected. These settings work the same way for headless password reset as they do for headless registration. Similarly, we recommend requiring reCAPTCHA and not authentication for this example. 3. For the maximum number of password reset attempts, keep the default of 5 attempts. 5. Configure reCAPTCHA options. The reCAPTCHA options apply to Headless Registration API and Headless Forgot Password API. 1. For Secret Key, enter the secret key from your reCAPTCHA API key pair. You get this information from Google when you set up reCAPTCHA. 2. For Score Threshold, enter 0.7. The score threshold is the lowest value that you accept for the reCAPTCHA score issued by Google. This score helps you determine whether new registration requests are valid. Scores closer to 0 are more likely to be bots, while scores closer to 1 are more likely to be valid users. The minimum score threshold that Salesforce allows is 0.5. 6. Save your settings. # Configure Your AWS Account to Integrate Amazon RDS via Salesforce Connect The high-level configuration steps to connect and integrate data from Amazon RDS into Salesforce are grouped into setup tasks to be performed in AWS and in Salesforce. We recommend that you follow this simple yet complete example configuration to understand the relevant concepts. You can then use these patterns to solve your target use cases. [Setup Tasks to Configure AWS](https://developer.salesforce.com/docs/platform/sf-connect-amazon-rds/guide/setup-AWS.md) 1. Obtain an AWS account. 2. Run the CloudFormation template. 3. Note the database name and credentials in Secrets Manager. 4. Validate the Amazon RDS configuration. 5. Test the AppSync’s connection to the database. 6. Validate AppSync API’s authentication settings. [Setup Tasks to Configure Salesforce](https://developer.salesforce.com/docs/platform/sf-connect-amazon-rds/guide/setup-SF.md) 1. Obtain access to a Salesforce org. 2. Define a Named Credential. 3. Create an external data source and external objects. 4. Surface Amazon RDS data in Salesforce UI. # Considerations Keep these considerations and limitations in mind while using restriction rules. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| ## Available Objects - Before creating restriction rules, we recommend that you [Turn Off Salesforce Classic for Your Org](https://help.salesforce.com/s/articleView?id=xcloud.lex_enable_users_disable_switcher.htm&type=5). Salesforce can't guarantee that restriction rules work as intended for end users who are in the Salesforce Classic experience. - Restriction rules are available for custom objects, external objects, contracts, events, quotes, tasks, time sheets, and time sheet entries. - In calendars, if the Show Details access level is selected, users can see the subject of all events, regardless of the restriction rules created. For more information, see [Share Your Calendar in Lightning Experience](https://help.salesforce.com/articleView?id=calendar_sharing_share_lex.htm) in Salesforce Help. ## Applicable Features - Restriction rules are applied to the following Salesforce features: - Links - List Views - Lookups - Records - Related Lists - Reports - Search - SOQL - SOSL - Restriction rules support custom picklist values in record and user criteria. If you delete a custom picklist value used in a restriction rule, the rule no longer works as intended. - If a restriction rule's record criteria uses a lookup field and the related record doesn't exist, access isn't granted. - Use the Activity Timeline instead of activity related lists, such as Open Activities or Activity History. If you use activity related lists, create rules on task or event objects using fields that are only available in the related lists. - If you use Open Activities and Activity History related lists, when restriction rules are applied, it’s possible that fewer than 50 records are displayed when more activities exist that the user has access to. This behavior occurs because these lists display at most 50 records, and restriction rules are applied after. This behavior is related to the known issue[, Limit of Fifty Records Visible in Related List View](https://trailblazer.salesforce.com/issues_view?id=a1p3A000000ehkQQAQ). - After restriction rules are applied, users can still see records that they previously had access to in the global search box shortcuts. When users click the record name, they can't access the record and get an error. - Users can see their subordinates' events in calendars even if the users have an active restriction rule applied. - If a user creates an event or a task record using the Chatter publisher, the record name is visible in the related Chatter post. Restriction rules don’t restrict visibility to these record names. - Users can’t clone records that have a lookup to a record that they can’t see due to a restriction rule. For example, you have a restriction rule that prevents a user from seeing a specific contract record, and the user tries to clone an order record that has a lookup to the contract record. The user gets an error, preventing the clone operation from succeeding. - Restriction rules aren’t applied for code executed in System Mode. - Users with the View All Records or View All Data permissions can view all records regardless of restriction rules. Users with the Modify All Records or Modify All Data permissions can view, edit, and delete all records regardless of restriction rules. - A user with a restriction rule applied might not find all possible matching results when searching for a record. For performance reasons, search crowding applies limits to the number of search results. The record the user is looking for can fall outside those limits. Learn how to adjust your searches for the best results at [How Search Crowding Affects Search Results](https://help.salesforce.com/s/articleView?id=ai.search_crowding_considerations.htm&type=5). - The [UserRecordAccess](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_userrecordaccess.htm) object doesn’t consider whether a user’s access is blocked due to a restriction rule. If a user’s access is blocked even though query results state that they should have access, check to see if a restriction rule on the object prevents the user’s access. ## Creating Restriction Rules - You can create up to two active restriction rules per object in Enterprise and Developer Editions and up to five active restriction rules per object in Performance and Unlimited Editions. - Create only one restriction or scoping rule per object per user. In other words, for a given object, only one restriction or scoping rule at most should have the `userCriteria` field evaluate to `true` for a given user. - Creating a restriction rule for an object doesn’t automatically restrict access to its child objects. For example, if you create a restriction rule for the Contract object, the access doesn’t change for notes that are associated with the affected contract records. To secure these child objects, you must use other sharing mechanisms. - We recommend that you don’t edit the `targetEntity` field after the restriction rule is created. Instead, delete the existing rule and create a restriction rule with the correct values. - When you reference the `Owner` field, you must specify the object type in your syntax. For example, the `Owner` field on an Event object can contain a user or a queue, but queues aren’t supported in restriction rules. So it’s necessary to specify Owner:User in the `recordFilter` syntax when the filter allows only users. - If you reference IDs in the `recordFilter` field, use 15-character IDs instead of 18-character IDs. - You can reference another object’s field using dot notation in the `recordFilter` field. You can use only one “dot” (one lookup level from the `targetEntity`). For example, `Owner.UserRoleId`. See [Allow Users to See Only Records Owned by Same Role](https://developer.salesforce.com/docs/platform/restriction-rules/guide/examples/restriction-rules-example-role.md) and [Allow Users to See Only Records Owned by Same Profile](https://developer.salesforce.com/docs/platform/restriction-rules/guide/examples/restriction-rules-example-profile.md) for examples. - We support these data types in the `recordFilter` and `userCriteria` fields: - boolean - date - dateTime - double - int - reference - string - time - single picklist **note:** Comma-separated ID or string values are supported in the Record Criteria field. - Including a null or blank value in record criteria isn't supported and can result in unexpected behavior. - Restriction rules support only the EQUALS operator. The AND, OR, or any other operators aren’t supported. - The use of formulas isn’t supported. - Don't create rules on Event.IsGroupEvent, which indicates whether the event has invitees. - You can use a change set or unlocked package to move restriction rules from one org to another. - If you include IDs in your `recordFilter` or `userCriteria` fields that are specific to your Salesforce org (such as a role, record type, or profile ID), you must modify these IDs in the target org if different from the org where the restriction rules were originally created. Keep this consideration in mind if deploying rules between sandboxes or to a production org. ## Restriction Rules and External Objects - Only external objects created using the Salesforce Connect: OData 2.0, OData 4.0, and Cross-Org adapters support restriction rules. - External objects created using the Cross-Org adapter don’t support search or SOSL when a rule is applied to a user. Salesforce returns only search results that match the most recently viewed records. - External objects created using the Salesforce Connect custom adapter aren’t supported. - External object record data is stored outside Salesforce. Admins are responsible for ensuring that rules they create on external objects don’t negatively impact performance in Salesforce or in the external system. **important:** - Editing or deleting a restriction rule on an external object causes an additional database call, which can result in additional billing when the external data source bills per call. - When search is enabled for external object records, searching requires additional database calls each time. Avoid additional charges by turning off search for external object records. As with all restriction rules, using only object fields that are indexed is recommended, especially in record criteria. - Using external IDs in record criteria isn’t recommended. - Restriction rules for external objects don’t include organization-wide defaults or sharing mechanisms. - External objects don’t appear in Object Manager. To navigate to an external object, enter `External Data Sources` in the Quick Find box in Setup, then select **External Data Sources**. Select an external object from the list view on this page. **note:** You can also find external objects in the Most Recently Used list in Setup. ## Performance Considerations Restriction rules were built to support sharing needs in a performant way. Your data volume and architecture are also factors in rule performance. - To test a rule’s performance impact, take the record criteria to your API client of choice and run the query. If it’s fast for a given user, the rule is likely to run efficiently. For objects with large data volumes, add three to five percent overhead to the record filter’s performance. - If it isn’t performant, isolate the field that is slowing performance. Work with Salesforce customer support to find out if the field can be indexed. **See Also** - [*Knowledge Article*: Improve Performance of SOQL Queries using a Custom Index](https://help.salesforce.com/apex/HTViewSolution?urlname=Custom-indexes-for-an-organization-to-help-improve-performance&language=en_US) # Considerations for Scoping Rules Familiarize yourself with these considerations for using scoping rules. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance** and **Unlimited** Editions| ## Creating Scoping Rules Your edition affects how many active rules you can have. - Create up to two active scoping rules per object in Developer editions. - Create up to five active scoping rules per object in Performance and Unlimited editions. - Create only one scoping or restriction rule per object per user. For a given object, only one scoping or restriction rule’s `userCriteria` field can evaluate to `true` for a given user. - In reports that contain data for multiple objects, all relevant scoping rules are applied when **Filter by scope** is selected. For example, if you have a report on opportunities with added account fields, both the opportunity and account scoping rules are considered when filtering the records. - Creating a scoping rule for an object impacts only that object and doesn’t affect child objects. - When you reference the `Owner` field, you must specify the object type in your syntax. For example, the `Owner` field on an event object can contain a user or a queue, but queues aren’t supported in scoping rules. So it’s necessary to specify Owner:User in the `recordFilter` syntax when the filter allows only users. - You can reference another object’s field using dot notation in the `recordFilter` field. You can use only one “dot” (one lookup level from the `targetEntity`). For example, `Owner.UserRoleId`. - These data types are supported in the `recordFilter` and `userCriteria` fields. - boolean - date (yyyy-MM-dd) - dateTime (yyyy-MM-dd HH:mm:ss) - double - int - reference - string - time - single picklist **note:** Comma-separated ID or string values are supported in the Record Criteria field. - Including a null or blank value in record criteria isn't supported and can result in unexpected behavior. - Don't create rules on Event.IsGroupEvent, which indicates whether the event has invitees. - Use the Activity Timeline instead of Open Activities or Activity History. If you use Open Activities and Activity History related lists, create rules on task or event objects using fields that are only available on the OpenActivity and ActivityHistory objects. - For list views and reports, you can apply the scope through Metadata API (using the `filterScope` field on the ListView type and the `scope` field on the Report type “scope”). - If you include an ID in your `recordFilter` or `userCriteria` field that’s specific to your Salesforce org (such as a role, record type, or profile ID), you must modify the ID in the target org if it’s different from the org where the scoping rule was originally created. Keep this consideration in mind when deploying rules between sandboxes or to a production org. ## Using SOQL - You can use a SOQL operator in record criteria only when creating scoping rules via the API. - Unless you use SOQL, scoping rules support only the EQUALS operator. The AND and OR operators aren’t supported. - When using the SOQL operator in the record filter, the SELECT statement, including nested SELECT statements, must include USING SCOPE EVERYTHING. USING SCOPE EVERYTHING is the only valid scope clause syntax for scoping rules. - The SOQL operator doesn't support $User syntax except for $User.Id. Dynamic queries within the SOQL operator aren't supported, including on other user object fields. ## Example Supported SOQL Syntax ``` SOQL(Id, SELECT Account.id FROM AccountAdvisors USING SCOPE EVERYTHING WHERE userid = $User.Id) ``` Unsupported SOQL Syntax ``` SOQL(Id, SELECT Account.id FROM AccountAdvisors USING SCOPE EVERYTHING WHERE userid = $User.Current_Advisor__c) ``` - Using the same object as the SOQL Query object and the Scoping Rule object isn’t supported. - The left operand in the SOQL type RecordCriteria must query a single ID (primary key) or reference (foreign key) field. See Comparison Operators for a list of valid operators that you can use in the field expression of a `WHERE` clause, which you use in a `SELECT` statement. ## Example `"recordFilter":"SOQL(OwnerId, Select Id from User USING SCOPE Everything LIMIT 2)"` The left operand is OwnerId in this example. - These objects aren't supported in the SOQL operator: - ActivityHistory - Attachments - Event - EventAttendee - Note - OpenActivity - Tags (AccountTag, ContactTag, and all other tag objects) - Task ## Modifying Scoping Rules - We recommend not editing the `targetEntity` field after a scoping rule is created. Instead, delete the rule and create another one with the correct values. - To disable a scoping rule, first delete the list views and reports that have **Filter by scope** selected. After a scoping rule is disabled, the list views and reports aren't functional nor modifiable. - The scoping rule `userCriteria` field supports custom permissions. If you delete the custom permission, the scoping rules that use the custom permissions don’t work. - Scoping rules support custom picklist values in record filter and user criteria. If you delete a custom picklist value used in a scoping rule, the rule no longer works as intended. ## Accounts, Contacts, and Person Accounts - Scoping rules don't support IsPersonAccount fields on the account object. When setting a scoping rule, don't use IsPersonAccount fields such as PersonDepartment or PersonLeadSource in record filter criteria. Find a list of IsPersonAccount fields on the [Account](https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_account.htm) page. - An error can result if you navigate to a person account detail page from a Contacts list view. To navigate to a person account detail page when there's a scoping rule on the account object, use an Accounts list view such as All Accounts. - In related lists, all associated records that a user can access are visible, regardless of scope, except in the contact role related list. When a scoping rule is applied on the contact object, scope is applied to the[ contact role](https://help.salesforce.com/s/articleView?id=sales.sales_core_contact_roles.htm&type=5) related list that appears on account, opportunity, case, and contract records. So it’s possible that users, such as members of a sales team, see a filtered set of contact roles without knowing that the list is filtered. - When an org uses duplicate rules to prevent creating duplicate records, scoping rules limit the potential duplicates that are shown, even when **Bypass sharing rules** is turned on. Duplicate records are limited by the scope set in the scoping rule. ## Performance Considerations - Scoping rules were built to support sharing needs in a performant way. Your data volume and architecture are factors in rule performance. Salesforce reserves the right to disable a scoping rule if a rule you create is inefficient or if your data model has so much data that scoping rules cause slowness when applied. To prevent throttling or deactivation, test the scoping rules that you plan to apply in a sandbox environment before enabling them in production. - To test the performance impact of a rule that uses a SOQL operator, take the SOQL statement and run it in your API client of choice. If it’s fast for a given user, the rule is likely to run efficiently. - If a rule isn’t performant, isolate the field that’s slowing performance. Work with Salesforce customer support to find out if the field can be indexed. **See Also** - [*Knowledge Article*: Improve Performance of SOQL Queries using a Custom Index](https://help.salesforce.com/apex/HTViewSolution?urlname=Custom-indexes-for-an-organization-to-help-improve-performance&language=en_US) - [*SOQL and SOSL Reference*: Comparison Operators](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_comparisonoperators.htm) # Create a Branch Management Scoping Rule Using the Metadata API Create a scoping rule that filters account records based on a banker’s branch location. This example uses the branch management data model included in Financial Services Cloud and the RestrictionRule Metadata API type. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance** and **Unlimited** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage scoping rules:|Manage Sharing| |To view scoping rules:|View Setup & Configuration AND View Restriction and Scoping Rules| This example uses the SOQL operator in its `recordFilter` to identify the accounts that match a retail banker’s branch location. Check out the [Branch Management data model](https://developer.salesforce.com/docs/atlas.en-us.financial_services_cloud_admin_guide.meta/financial_services_cloud_admin_guide/fsc_admin_branch.htm) to understand the objects used in this example and how they relate to each other. 1. Use the [RestrictionRule](https://developer.salesforce.com/docs/atlas.en-us.restriction_rules.meta/restriction_rules/meta_restrictionrule.htm) type to create and manage both restriction rules and scoping rules. Set up the `package.xml` manifest file and your directory. Example `package.xml` file: ```nolang * RestrictionRule 68.0 ``` Example directory: ```nolang myPackage/package.xml myPackage/restrictionRules myPackage/restrictionRules/Rule1.rule myPackage/restrictionRules/Rule2.rule ``` 2. Include all required fields. In this example, we used these values. ```nolang true Scoping rule where users can scope account records by the user’s current branch Scoping BranchRuleOnAccount SOQL(Id, SELECT AccountId FROM BranchUnitCustomer USING SCOPE EVERYTHING WHERE BranchUnitId IN(SELECT CurrentBranchId From Banker WHERE UserOrContactId = $User.Id)) Account $User.IsActive = true 1 ``` This example also uses objects from the [Branch Management data model](https://developer.salesforce.com/docs/atlas.en-us.financial_services_cloud_admin_guide.meta/financial_services_cloud_admin_guide/fsc_admin_branch.htm) and sets a scoping rule that shows users lead records related to the user’s current branch location. ```nolang true Scoping rule where users can scope lead records by their current branch Scoping BranchRuleOnLead SOQL(Id, SELECT RelatedRecordId FROM BranchUnitRelatedRecord USING SCOPE EVERYTHING WHERE BranchUnitId IN(SELECT CurrentBranchId From Banker WHERE UserOrContactId = $User.Id)) Lead $User.IsActive = true 1 ``` To create a similar scoping rule for a different object, adjust the `targetEntity` field to include case, contact, or another supported standard or custom object. 3. Zip your directory, and deploy your changes. For more information, see [Deploying and Retrieving Metadata](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/file_based.htm) in the Metadata API Developer Guide. Let’s take a closer look at the Branch Rule On Account example’s SOQL operator. `SOQL(Id, SELECT AccountId FROM BranchUnitCustomer USING SCOPE EVERYTHING WHERE BranchUnitId IN(SELECT CurrentBranchId From Banker WHERE UserOrContactId = $User.Id))` - The SOQL statement takes the `Id` from the account object, which is the target entity whose records the scoping rule filters, and selects `AccountId` from the `BranchUnitCustomer` object. - The where clause gets the `BranchUnitId`, which is a unique identifier of each branch, from a nested query. The nested query finds each banker’s current branch from the `Banker` object by matching the `UserOrContactId` to the currently logged-in user. When writing scoping rules using SOQL, follow these guidelines. - In SOQL operators, the SOQL query object and the scoping rule target entity can’t be the same object. In this example, the SOQL query object is `BranchUnitCustomer` and the scoping rule object, called the `targetEntity`, is `account`. - In the SOQL type `RecordCriteria`, the left operand must query a single ID (primary key) or reference (foreign key) field. In this example, the left operand is a field on the target entity called `Id`. For more tips about writing scoping rules using a performant SOQL operator, see Scoping Rules Considerations. The SOQL operator is supported for scoping rules only. - **[Retrieve and Update Information](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/guide/scoping-rules-quickstart-metadata-calls.md)** Use the `deploy()` and `retrieve()` calls to move metadata (XML files) between Salesforce and a local file system. You can delete scoping rules by using the same procedure used to deploy components and including a delete manifest file. **See Also** - [Considerations for Scoping Rules](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/scoping-rules-considerations.md) - [Restriction Rule Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.scoping_rules.meta/scoping_rules/scoping_rules_about.htm) # Create a Branch Management Scoping Rule Using the Tooling API Create a scoping rule that filters account records based on a banker’s branch location. This example uses the branch management data model included in Financial Services Cloud and the RestrictionRule Tooling API object. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance** and **Unlimited** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage scoping rules:|Manage Sharing| |To view scoping rules:|View Setup & Configuration AND View Restriction and Scoping Rules| This example uses the SOQL operator in its `recordFilter`. Check out the [Branch Management data model](https://developer.salesforce.com/docs/atlas.en-us.financial_services_cloud_admin_guide.meta/financial_services_cloud_admin_guide/fsc_admin_branch.htm) to understand the objects used in this example and how they relate to each other. 1. Use the RestrictionRule object to create and manage both restriction rules and scoping rules. Include the `FullName` value and all required fields. For more information, see the reference topic [RestrictionRule](https://developer.salesforce.com/docs/atlas.en-us.restriction_rules.meta/restriction_rules/tooling_api_objects_restrictionrule.htm). In this example, we used these values. **note:** The `userCriteria` in this example applies this rule to any active user in your org. Adjust the `userCriteria` if the rule must apply to a different subset of your users. ```nolang { "FullName": "BranchRuleOnAccount", "Metadata": { "active": true, "description": "Scoping rule where users can scope account records by the user’s current branch", "enforcementType": "Scoping", "masterLabel": "BranchRuleOnAccount", "recordFilter": "SOQL(Id, SELECT AccountId FROM BranchUnitCustomer USING SCOPE EVERYTHING WHERE BranchUnitId IN(SELECT CurrentBranchId From Banker WHERE UserOrContactId = $User.Id))", "targetEntity": "Account", "userCriteria": "$User.IsActive = true", "version": 1 } } ``` This example also uses objects from the [Branch Management data model](https://developer.salesforce.com/docs/atlas.en-us.financial_services_cloud_admin_guide.meta/financial_services_cloud_admin_guide/fsc_admin_branch.htm) and sets a scoping rule that shows users lead records related to the user’s current branch location. ```nolang { "FullName": "BranchRuleOnLead", "Metadata": { "active": true, "description": "Scoping rule where users can scope lead records by the user’s current branch", "enforcementType": "Scoping", "masterLabel": "BranchRuleOnLead", "recordFilter": "SOQL(Id, SELECT RelatedRecordId FROM BranchUnitRelatedRecord USING SCOPE EVERYTHING WHERE BranchUnitId IN(SELECT CurrentBranchId From Banker WHERE UserOrContactId = $User.Id))", "targetEntity": "Lead", "userCriteria": "$User.IsActive = true", "version": 1 } } ``` To create a similar scoping rule for a different object, adjust the `targetEntity` field to include case, contact, or another supported standard or custom object. 2. To create the scoping rule, use a POST request in the tooling API. `POST /services/data/v`68.0`/tooling/sobjects/RestrictionRule` 3. Copy your scoping rule definition into the request body. 4. Execute your request. Copy the ID returned for the scoping rule for later reference. Let’s take a closer look at the Branch Rule On Account example’s SOQL operator. `SOQL(Id, SELECT AccountId FROM BranchUnitCustomer USING SCOPE EVERYTHING WHERE BranchUnitId IN(SELECT CurrentBranchId From Banker WHERE UserOrContactId = $User.Id))` - The SOQL statement takes the `Id` from the account object, which is the target entity whose records the scoping rule filters, and selects `AccountId` from the `BranchUnitCustomer` object. - The where clause gets the `BranchUnitId`, which is a unique identifier of each branch, from a nested query. The nested query finds each banker’s current branch from the `Banker` object by matching the `UserOrContactId` to the currently logged-in user. When writing scoping rules using SOQL, follow these guidelines. - In SOQL operators, the SOQL query object and the scoping rule target entity can’t be the same object. In this example, the SOQL query object is `BranchUnitCustomer` and the scoping rule object, called the `targetEntity`, is `account`. - In the SOQL type `RecordCriteria`, the left operand must query a single ID (primary key) or reference (foreign key) field. In this example, the left operand is a field on the target entity called `Id`. For more tips about writing scoping rules using a performant SOQL operator, see Scoping Rules Considerations. The SOQL operator is supported for scoping rules only. - **[Retrieve and Update Information](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/guide/scoping-rules-quickstart-tooling-methods.md)** Use the GET, PATCH, and DELETE methods to retrieve, update, and delete scoping rules. **See Also** - [Considerations for Scoping Rules](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/scoping-rules-considerations.md) - [Restriction Rule Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.scoping_rules.meta/scoping_rules/scoping_rules_about.htm) # Create a Headless Registration Handler Create an Apex class for your registration handler. You reference this Apex class when you configure Experience Cloud settings on the Login & Registration page. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From Setup, in the Quick Find box, enter `Apex`, and then select **Apex Classes**. 2. Click **New**. 3. Fill the class with your headless registration handler code. You can paste in the code from the example. 4. Save the class, and note its name. ## Example Here’s an example headless registration handler. This registration handler creates a user and links it to the account and profile that you created earlier in this guide. **note:** This sample code is for demonstration only. Always test code before deploying it to a production environment. ```nolang /* * Sample Headless Self Registration Handler class for Headless Identity implementation */ global class HeadlessSelfRegistrationHandler implements Auth.HeadlessSelfRegistrationHandler{ static final String headless_account = 'My Account'; // Creates a Standard salesforce or a community user global User createUser(Id profileId, Auth.UserData data, String customUserDataMap, String experienceId, String password){ User u = new User(); //Ensures the user will save as all required fields are pre-filled in with dummy values prepareUserData(data, u); //Get the Account, and create it if one is not already present. Account a; List accounts = [SELECT Id FROM Account WHERE name='My Account']; if(accounts.isEmpty()) { a = new Account(name = headless_account); insert(a); } else { a = accounts[0]; } handleCustomData(customUserDataMap); // Create the Contact Contact c = new Contact(); c.accountId = a.Id; c.firstName = u.firstName; c.lastName = u.lastName; insert(c); //Associate the Contact to the user along with the profile. u.profileId = profileId; u.contactId = c.Id; return u; } /* * We support the ability to pass in complex structures in the custom user data map * You can build Apex classes that represent your complex structure * Then deserialize that structure into your Apex class * In this case we have a class at the bottom of this file called "ContactInformation" * This class deserializes the incoming request and prints out the fields * */ void handleCustomData(String customUserDataMap) { System.debug('Custom Data: ' + customUserDataMap); ContactInformation contactInfo = null; try { contactInfo = (HeadlessSelfRegistrationHandler.ContactInformation)JSON.deserialize(customUserDataMap, HeadlessSelfRegistrationHandler.ContactInformation.class); System.debug('ContactInfo.mobilePhone: ' + contactInfo.mobilePhone); System.debug('ContactInfo.streetAddress: ' + contactInfo.streetAddress); System.debug('ContactInfo.city: ' + contactInfo.city); System.debug('ContactInfo.state: ' + contactInfo.state); } catch (Exception e) { System.debug('JSON was not formed correctly for the apex class'); } } /* * This method handles filling user data that is required by Salesforce but is not passed in during registration * It is not strictly necessary but helpful as it centralizes the management of unnecessary fields to the IDP instead of the client. */ void prepareUserData(Auth.UserData data, User u){ String name, firstName, lastName, username, alias, email; System.debug('----> Passed In User Information'); System.debug('Email: ' + data.email); System.debug('First Name: ' + data.firstName); System.debug('Last Name: ' + data.lastName); for(String key : data.attributeMap.keySet()) { System.debug('key: ' + key + ' value: ' + data.attributeMap.get(key)); } // Initialize the attributes required to create a new user with dummy values // in case they are not provided by the Auth Provider firstName = 'change-me'; lastName = 'change-me'; email = 'change@me.com'; if(data.email != null && data.email != '') email = data.email; if(data.firstName != null && data.firstName != '') firstName = data.firstName; if(data.LastName != null && data.lastName != '') lastName = data.lastName; if(data.attributeMap.containsKey('full_name')) name = data.attributeMap.get('full_name'); if(data.attributeMap.containsKey('name')) name = data.attributeMap.get('name'); if(firstName == 'change-me' && name != '') firstName = name.substringBefore(' '); if(lastName == 'change-me' && name.substringAfter(' ') != '') lastName = name.substringAfter(' '); // Generate a random username Integer rand = Math.round(Math.random()*100000000); if(data.attributeMap.containsKey('username')){ username = data.attributeMap.get('username'); }else{ username = lastName + '.' + rand + '@social-sign-on.com'; } alias = firstName; //Alias must be 8 characters or less if(alias.length() > 8) alias = alias.substring(0, 8); u.username = username; u.email = email; u.lastName = lastName; u.firstName = firstName; u.alias = alias; u.languagelocalekey = UserInfo.getLocale(); u.localesidkey = UserInfo.getLocale(); u.emailEncodingKey = 'UTF-8'; u.timeZoneSidKey = 'America/Los_Angeles'; } /* * Apex Class Representation of Contact Information * which was passed in the custom data map * */ global class ContactInformation { String mobilePhone; String streetAddress; String city; String state; Boolean privacyPolicy; } } ``` # Create a Restriction Rule Using the Tooling API Create a restriction rule that controls which tasks members of the Sales Team can access using the RestrictionRule Tooling API object. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage restriction rules:|Manage Sharing| |To view restriction rules:|View Setup & Configuration AND View Restriction and Scoping Rules| You can create up to two restriction rules per object in Enterprise and Developer Editions and up to five restriction rules per object in Performance and Unlimited Editions. 1. Set a value for the `FullName` value (the full name of the associated metadata object in Metadata API). We don't support two consecutive underscores in the `FullName` field. 2. Include all other required fields. For more information, see the reference topic [RestrictionRule](https://developer.salesforce.com/docs/atlas.en-us.restriction_rules.meta/restriction_rules/tooling_api_objects_restrictionrule.htm). For our example, we set the fields as follows: ```nolang { "FullName": "restrictionrulesalesteam", "Metadata": { "active": true, "description": "Sales team can see only task records with specified record type", "enforcementType": "Restrict", "masterLabel": "Sales Team Record Type", "recordFilter": "recordTypeId = '011xxxxxxxxxxxx'", "targetEntity": "Task", "userCriteria": "$User.ProfileId = '00exxxxxxxxxxxx'", "version": 1 } } ``` 3. Use a POST request to create the restriction rule. `POST /services/data/v55.0/tooling/sobjects/RestrictionRule` 4. Copy your restriction rule definition into the request body. 5. Execute your request. Note the ID returned for the newly created restriction rule for later reference. - **[Retrieve and Update Information](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-quickstart-tooling-methods.md)** Use the GET, PATCH, and DELETE methods to retrieve, update, and delete restriction rules. # Create a Role to Manage Headless Identity Features For identity and access management, it’s important to define who can access what. Create a role to ensure that you have the right level of access to manage Headless Identity features. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| |User Permissions Needed| | |-----------------------|--| |To view roles and role hierarchy:|View Roles and Role Hierarchy| |To create, edit, and delete roles:|Manage Roles| |To assign users to roles:|Manage Internal Users| 1. From Setup, in the Quick Find box, enter `Roles`, and then select **Roles**. 2. Click **Set Up Roles**. 3. Under CEO, select **Add Role**. 4. Enter a label for the role, such as `Headless Identity Admin`. The Role Name autofills. You can keep it the same or change it. 5. Save your changes. Stay on this Setup page to complete the next step. # Create a Wealth Management Scoping Rule Using the Metadata API Create a scoping rule that shows a sales support associate who supports multiple financial advisors only the record set that corresponds to the financial advisor that the associate is working with. Use the RestrictionRule Metadata API type. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance** and **Unlimited** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage scoping rules:|Manage Sharing| |To view scoping rules:|View Setup & Configuration AND View Restriction and Scoping Rules| **note:** You can use the RestrictionRule object to create and manage both restriction rules and scoping rules. For information on restriction rules, see the Restriction Rule Developer Guide. 1. Set up the `package.xml` manifest file and your directory. Example `package.xml` file: ```nolang * RestrictionRule 68.0 ``` Example directory: ```nolang myPackage/package.xml myPackage/restrictionRules myPackage/restrictionRules/Rule1.rule myPackage/restrictionRules/Rule2.rule ``` 2. Include all required fields. For more information, see the reference topic [RestrictionRule](https://developer.salesforce.com/docs/atlas.en-us.restriction_rules.meta/restriction_rules/meta_restrictionrule.htm). In this example, we used these values. ```nolang true Sales support associate sees only account records of specified advisor Scoping Advisor1 Record Type recordTypeId = '012xxxxxxxxxxxx' Account $User.ProfileId = '00exxxxxxxxxxxx' 1 ``` 3. Zip your directory, and deploy your changes. For more information, see [Deploying and Retrieving Metadata](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/file_based.htm) in the Metadata API Developer Guide. # Create a Wealth Management Scoping Rule Using the Tooling API Create a scoping rule that shows a sales support associate who supports multiple financial advisors only the record set that corresponds to the financial advisor that the associate is working with. Use the RestrictionRule Tooling API object. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance** and **Unlimited** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage scoping rules:|Manage Sharing| |To view scoping rules:|View Setup & Configuration AND View Restriction and Scoping Rules| **note:** You can use the RestrictionRule object to create and manage both restriction rules and scoping rules. For information on restriction rules, see the Restriction Rule Developer Guide. This example uses the SOQL operator in the recordFilter field to determine which accounts to display to the user based on the account, team, and user entitlements. ![Diagram illustrating the entitlement junction objects that connect the client (account) object to the user object, determining the user's access.](https://developer.salesforce.com/media/scoping-rules-dev-guide/scoping_rules_wealth_mgmt_diagram.png) 1. Set a value for the `FullName` value (the full name of the associated metadata object in Metadata API). 2. Include all other required fields. For more information, see the reference topic [RestrictionRule](https://developer.salesforce.com/docs/atlas.en-us.restriction_rules.meta/restriction_rules/tooling_api_objects_restrictionrule.htm). In this example, we used these values. ```nolang { "FullName": "SalesSupportAssociateScopingRule", "Metadata": { "active": true, "description": "Sales support associate sees only account records of of Advisor1", "enforcementType": "Scoping", "masterLabel": "Advisor1 Record Set", "recordFilter": "SOQL(id, SELECT Account__c FROM Client_Entitlement__c USING SCOPE EVERYTHING WHERE Team_Entitlement__c IN ( SELECT Team_Entitlement__c FROM User_Entitlement__c USING SCOPE EVERYTHING WHERE User__c = $User.id) )", "targetEntity": "Account", "userCriteria": "$User.ProfileId = '00exxxxxxxxxxxx'", "version": 1 } } ``` 3. Use a POST request to create the scoping rule. `POST /services/data/`68.0`/tooling/sobjects/RestrictionRule` 4. Copy your scoping rule definition into the request body. 5. Execute your request. Copy the ID returned for the scoping rule for later reference. # Create an Account for End Users You must use an account to store information about your end users, including their contact records. For Headless Identity, you reference the account in your Apex registration handlers. When new users log in via an authentication provider or sign up directly through your site, they’re added as contacts. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| Salesforce supports two types of accounts: business accounts, which store information about organizations, and person accounts, which store information about individuals. For this example, you use a business account to keep all your end-user records in one place. 1. From the App Launcher, find and select **Accounts**. 2. Click **New**. 3. For the Account Name, enter `My Account`. You can leave the rest of the details blank. 4. Save your new account. # Create an Authentication Provider Registration Handler Create an Apex registration handler to use with your authentication provider. When users log in to your third-party app with an external single sign-on (SSO) provider, the registration handler creates and updates their user records. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From Setup, in the Quick Find box, enter `Apex`, and then select **Apex Classes**. 2. Click **New**. 3. Fill the class with your registration handler code. You can paste in the code from the example. 4. Save the class, and note its name. ## Example Here’s an example registration handler that you can use with your Google authentication provider. This class is triggered every time a user logs in with Google. If it’s the user’s first time logging in to your app, the class creates a user in Salesforce and associates them with the Headless Identity Demo profile that you created. The class also added the user to the account that you created earlier. If the user logged in to your app before, the class updates their record with any new information. **note:** This sample code is for demonstration only. Always test code before deploying it to a production environment. ```apex /* * This class is a sample auth provider registration handler for a Headless Identity implementation * It creates an external user and associates them with the Headless Demo Profile * It creates or associates the user contact to an Account called My Account * It uses the email as the username * */ global class HeadlessDemoGoogleIDPRegistrationHandler implements Auth.RegistrationHandler{ static final String headless_account = 'My Account'; static final String headless_profile = 'Headless Demo Profile'; /* * Tries to find a user with a username matching the incoming email * If not it creates one, associates it to an Account and Profile * */ global User createUser(Id portalId, Auth.UserData data) { //Find an existing user, we are using username to map to email as your google email is a google username List users = [SELECT Id, firstName, lastName, email FROM User where Username =:data.email LIMIT 1]; User u = null; if (!users.isEmpty()) { u = users[0]; } //If no user is found we create one if (u == null) { u = new User(); prepareUserData(data, u); //Get the Account, and create it if one is not already present. Account a; List accounts = [SELECT Id FROM Account WHERE name='My Account']; if(accounts.isEmpty()) { a = new Account(name = headless_account); insert(a); } else { a = accounts[0]; } // Get the Profile Profile p = [SELECT Id FROM Profile WHERE Name =: headless_profile LIMIT 1]; //Create the Contact Contact c = new Contact(); c.accountId = a.Id; c.firstName = u.firstName; c.lastName = u.lastName; insert(c); //Associate the Contact to the user along with the profile. u.profileId = p.Id; u.contactId = c.Id; } else { u.firstName = data.firstName; u.lastName = data.lastName; u.email = data.email; update u; } return u; } /* * Basic Update User Method * */ global void updateUser(Id userId, Id portalId, Auth.UserData data){ User u = new User(id=userId); u.email = data.email; u.lastName = data.lastName; u.firstName = data.firstName; update(u); } /* * This method handles filling user data that is required by Salesforce but is not passed in during registration */ void prepareUserData(Auth.UserData data, User u){ String name, firstName, lastName, username, alias, email; System.debug('----> Passed In User Information'); System.debug('Email: ' + data.email); System.debug('First Name: ' + data.firstName); System.debug('Last Name: ' + data.lastName); for(string key : data.attributeMap.keySet()) { system.debug('key: ' + key + ' value: ' + data.attributeMap.get(key)); } // Initialize the attributes essential for creating a new user with dummy values // in case they will not be provided by the Auth Provider firstName = 'change-me'; lastName = 'change-me'; email = 'change@me.com'; if(data.email != null && data.email != '') email = data.email; if(data.firstName != null && data.firstName != '') firstName = data.firstName; if(data.LastName != null && data.lastName != '') lastName = data.lastName; if(data.attributeMap.containsKey('full_name')) name = data.attributeMap.get('full_name'); if(data.attributeMap.containsKey('name')) name = data.attributeMap.get('name'); if(firstName == 'change-me' && name != '') firstName = name.substringBefore(' '); if(lastName == 'change-me' && name.substringAfter(' ') != '') lastName = name.substringAfter(' '); alias = firstName; //Alias must be 8 characters or less if(alias.length() > 8) alias = alias.substring(0, 8); u.username = email; u.email = email; u.lastName = lastName; u.firstName = firstName; u.alias = alias; u.languagelocalekey = UserInfo.getLocale(); u.localesidkey = UserInfo.getLocale(); u.emailEncodingKey = 'UTF-8'; u.timeZoneSidKey = 'America/Los_Angeles'; } } ``` # Create an OAuth Named Credential This example shows how to create an OAuth named credential in Apex to connect to GitHub. The general steps are: 1. If needed, create an external auth identity provider. 2. Create an external credential. Assign it an authentication protocol, create principals for authorization, and set other parameters. 3. Create a named credential, and link it to the external credential. 4. Check the status of the external credential. 5. Create permissions for the external credential. 6. Authenticate a user external credential. 7. Authenticate to an external system. 8. Use the named credential in a callout. ## Create an External Auth Identity Provider In this example, you create an external auth identity provider instead of a standard Salesforce authentication provider. Unlike standard authentication providers, you can package Apex code to create an external auth identity provider. The external auth identity provider object also offers additional configuration options so it’s easier to interact with an identity provider without writing custom Apex code. You use two separate Apex transactions to create the external auth identity provider metadata and its credentials. Separate transactions prevent mixed DML errors. First, create a method with the `Future` annotation that creates credentials asynchronously after you create external auth identity provider metadata. ```apex public class CreateIdpCredentialsAsync { @future public static void populateCredentials(String fullName) { // Populate external auth identity provider credentials ConnectApi.ExternalAuthIdentityProviderCredentialsInput credentialsInput = new ConnectApi.ExternalAuthIdentityProviderCredentialsInput(); credentialsInput.credentials = new List(); ConnectApi.ExternalAuthIdentityProviderCredentialInput clientId = new ConnectApi.ExternalAuthIdentityProviderCredentialInput(); clientId.credentialName = 'clientId'; clientId.credentialValue = '1234'; ConnectApi.ExternalAuthIdentityProviderCredentialInput clientSecret = new ConnectApi.ExternalAuthIdentityProviderCredentialInput(); clientSecret.credentialName = 'clientSecret'; clientSecret.credentialValue = '5678'; credentialsInput.credentials.add(clientId); credentialsInput.credentials.add(clientSecret); ConnectApi.NamedCredentials.createExternalAuthIdentityProviderCredentials(fullName, credentialsInput); } } ``` In a separate transaction, create an external auth identity provider record and call the `Future` method that you created to populate its credentials. ```apex // Create external auth identity provider metadata ConnectApi.ExternalAuthIdentityProviderInput idPInput = new ConnectApi.ExternalAuthIdentityProviderInput(); String fullName = 'GitHubProvider'; idPInput.fullName = fullName; idPInput.label = fullName; idPInput.authenticationProtocol = ConnectApi.IdentityProviderAuthProtocol.OAuth; idPInput.authenticationFlow = ConnectApi.IdentityProviderAuthFlow.AuthorizationCode; idPInput.authorizeUrl = 'https://www.test.com'; idPInput.tokenUrl = 'https://www.test.com'; ConnectApi.ExternalAuthIdentityProvider actualIdP = ConnectApi.NamedCredentials.createExternalAuthIdentityProvider(idPInput); CreateIdpCredentialsAsync.populateCredentials(fullName); ``` ## Create an External Credential An external credential contains the authentication and authorization information for the callout. When you create the external credential, you link it to the external auth identity provider. Later, you link the external credential to a named credential. ```apex ConnectApi.ExternalCredentialInput externalCredentialInput = new ConnectApi.ExternalCredentialInput(); externalCredentialInput.developerName = 'GitHubOAuth'; externalCredentialInput.masterLabel = 'GitHub OAuth'; externalCredentialInput.authenticationProtocol = ConnectApi.CredentialAuthenticationProtocol.OAuth; externalCredentialInput.principals = new List(); externalCredentialInput.parameters = new List(); // Specify GitHub as the external auth identity provider ConnectApi.ExternalCredentialParameterInput authProviderParam = new ConnectApi.ExternalCredentialParameterInput(); authProviderParam.parameterName = 'GitHubProvider'; authProviderParam.parameterType = ConnectApi.ExternalCredentialParameterType.ExternalAuthIdentityProvider authProviderParam.parameterValue = 'GitHubProvider'; externalCredentialInput.parameters.add(authProviderParam); // Populate principals to connect the external credential to permissions ConnectApi.ExternalCredentialPrincipalInput principalOne = new ConnectApi.ExternalCredentialPrincipalInput(); principalOne.principalName = 'Developer Access'; principalOne.principalType = ConnectApi.CredentialPrincipalType.PerUserPrincipal; principalOne.sequenceNumber = 1; principalOne.parameters = new List(); // Create a credential-level scope for GitHub access ConnectApi.ExternalCredentialParameterInput scopeParam = new ConnectApi.ExternalCredentialParameterInput(); scopeParam.parameterName = 'Scope'; scopeParam.parameterType = ConnectApi.ExternalCredentialParameterType.AuthParameter; scopeParam.parameterValue = 'gist'; principalOne.parameters.add(scopeParam); ConnectApi.ExternalCredentialPrincipalInput principalTwo = new ConnectApi.ExternalCredentialPrincipalInput(); principalTwo.principalName = 'Markdown Access'; principalTwo.principalType = ConnectApi.CredentialPrincipalType.NamedPrincipal; principalTwo.sequenceNumber = 2; externalCredentialInput.principals.add(principalOne); externalCredentialInput.principals.add(principalTwo); ConnectApi.ExternalCredential createdEC = ConnectApi.NamedCredentials.createExternalCredential(externalCredentialInput); ``` ## Create a Named Credential After you create an external credential, create a named credential, set the callout endpoint (URL), set callout options, and link the named credential to the external credential. ```apex ConnectApi.NamedCredentialInput namedCredentialInput = new ConnectApi.NamedCredentialInput(); namedCredentialInput.developerName = 'githubAPI'; namedCredentialInput.masterLabel = 'GitHub API'; namedCredentialInput.type = ConnectApi.NamedCredentialType.SecuredEndpoint; namedCredentialInput.calloutUrl = 'https://api.github.com'; namedCredentialInput.externalCredentials = new List(); ConnectApi.ExternalCredentialInput externalCredential = new ConnectApi.ExternalCredentialInput(); externalCredential.developerName = 'GitHubOAuth'; namedCredentialInput.externalCredentials.add(externalCredential); ConnectApi.NamedCredentialCalloutOptionsInput calloutOptions = new ConnectApi.NamedCredentialCalloutOptionsInput(); calloutOptions.allowMergeFieldsInBody = false; calloutOptions.allowMergeFieldsInHeader = false; calloutOptions.generateAuthorizationHeader = true; namedCredentialInput.calloutOptions = calloutOptions; ConnectApi.NamedCredential createdNamedCredential = ConnectApi.NamedCredentials.createNamedCredential(namedCredentialInput); ``` ## Create Permissions for the External Credential Here are examples of how to create and add permissions for these types of users. - A profile for a customer, using the **System Administrator** profile - Another customer profile, using the **Standard User** profile - A profile for an ISV, using permission sets ### Example: Create Permissions Using the System Administrator Profile ```apex String principalParamId; List principals = ConnectApi.NamedCredentials.getExternalCredential('GitHubOAuth').principals; for (ConnectApi.ExternalCredentialPrincipal principal : principals) { if (principal.principalName == 'Developer Access') { principalParamId = principal.id; } } String permSetId = [SELECT Id FROM PermissionSet WHERE Profile.Name = 'System Administrator' LIMIT 1].id; insert new SetupEntityAccess( ParentId = permSetId, SetupEntityId = principalParamId ); ``` ### Example: Create Permissions Using the Standard User Profile ```apex String principalParamId; List principals = ConnectApi.NamedCredentials.getExternalCredential('GitHubOAuth').principals; for (ConnectApi.ExternalCredentialPrincipal principal : principals) { if (principal.principalName == 'Markdown Access') { principalParamId = principal.id; } } String permSetId = [SELECT Id FROM PermissionSet WHERE Profile.Name = 'Standard User' LIMIT 1].id; insert new SetupEntityAccess( ParentId = permSetId, SetupEntityId = principalParamId ); ``` ### Example: Create Permissions Using a Permission Set Sometimes an ISV wants to use a permission set for authorization. ```apex // This script assumes you already have the permission set String principalParamId; List principals = ConnectApi.NamedCredentials.getExternalCredential('GitHubOAuth').principals; for (ConnectApi.ExternalCredentialPrincipal principal : principals) { if (principal.principalName == 'Developer Access') { principalParamId = principal.id; } } String permSetId = [SELECT Id FROM PermissionSet WHERE Name = 'DeveloperUsers' LIMIT 1].id; insert new SetupEntityAccess( ParentId = permSetId, SetupEntityId = principalParamId ); ``` ## Grant Access to User External Credentials A [user external credential](https://help.salesforce.com/s/articleView?id=xcloud.nc_user_external_credentials.htm&type=5) stores encrypted tokens used by named credentials. For most permission sets and profiles, Salesforce grants access to the user external credentials object by default. This example updates a permission set. In the rare case that you want to allow guest users to perform authenticated callouts, you must grant Read access to the User External Credentials object manually on the guest user profile. See [Configure the Guest User Profile](https://help.salesforce.com/s/articleView?id=platform.rss_config_guest_user_profile.htm) in Salesforce Help. ```apex ObjectPermissions objectPerms = [Select id, SobjectType, PermissionsRead, PermissionsCreate, PermissionsEdit, PermissionsDelete, PermissionsViewAllRecords, PermissionsModifyAllRecords from ObjectPermissions where parent.name ='SamplePermissionSet' and SobjectType='UserExternalCredential' Limit 1]; objectPerms.PermissionsRead = true; objectPerms.PermissionsCreate = true; objectPerms.PermissionsEdit = true; objectPerms.PermissionsDelete = true; objectPerms.PermissionsViewAllRecords = true; update objectPerms; ``` ## Authenticate to the External System Here are examples for authenticating by named principal and on a per-user basis. ### Example: Check the Authentication Status If you’re working programmatically, it can be useful to check the status of a credential to determine if users must authenticate. The external credential has an initial status of **Not Configured**. ```apex String externalCredential = 'GitHubOAuth'; String principalName = 'Developer Access'; ConnectApi.CredentialPrincipalType principalType = ConnectApi.CredentialPrincipalType.NamedPrincipal; ConnectApi.Credential actualCredential = ConnectApi.NamedCredentials.getCredential(externalCredential, principalName, principalType); System.debug(actualCredential.authenticationStatus); ``` Depending on whether you’re in a per-user scenario or using a named principal, you approach programmatic authentication differently. Named principals can be authenticated easily in the setup UI, though you can use code shown in [Example: Authenticate Using a Named Principal](#example-authenticate-using-a-named-principal) to access the URL needed for authentication. ### Example: Authenticate Using a Named Principal This code returns the URL an administrator would visit to authenticate a named principal on behalf of all users in the org. ```apex String externalCredential = 'GitHubOAuth'; ConnectApi.OAuthCredentialAuthUrlInput input = new ConnectApi.OAuthCredentialAuthUrlInput(); input.externalCredential = externalCredential; input.principalType = ConnectApi.CredentialPrincipalType.NamedPrincipal; input.principalName = 'Markdown Access'; ConnectApi.OAuthCredentialAuthUrl output = ConnectApi.NamedCredentials.getOAuthCredentialAuthUrl(input); // Users must open this link and authorize in the browser System.debug(output.authenticationUrl); ``` ### Example: Authenticate on a Per-User Basis Use code like this when building a custom UI to help users visit the URL required to authenticate against the external system. Again, in a per-user scenario, each user must visit this URL on their own before the integration can work. ```apex String externalCredential = 'GitHubOAuth'; ConnectApi.OAuthCredentialAuthUrlInput input = new ConnectApi.OAuthCredentialAuthUrlInput(); input.externalCredential = externalCredential; input.principalType = ConnectApi.CredentialPrincipalType.PerUserPrincipal; input.principalName = 'Developer Access'; ConnectApi.OAuthCredentialAuthUrl output = ConnectApi.NamedCredentials.getOAuthCredentialAuthUrl(input); // Users must open this link and authorize in the browser System.debug(output.authenticationUrl); ``` Congratulations. You have a working named credential. For tips on managing named and external credentials, such as deleting or changing them, see [Managing an OAuth Named Credential](https://developer.salesforce.com/docs/platform/named-credentials/guide/nc-manage-oauth-cred.md). For examples of how to use a named credential in your code, see [Use the Named Credential in a Callout](https://developer.salesforce.com/docs/platform/named-credentials/guide/nc-use-oauth-cred-in-callout.md). # Create an OAuth-Enabled External Client App Create your external client app for headless identity, add the required scopes, and enable the Authorization Code and Credentials flow at the app level. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. [Create an external client app](https://www.passkeycentral.org/introduction-to-passkeys/passkey-security) and set the distribution state to Local. 2. Under API (Enable OAuth Settings), select **Enable OAuth Settings**. 3. For Callback URL, enter `https://MyExperienceCloudSite.my.site.com/services/oauth2/echo`, where `https://MyExperienceCloudSite.my.site.com` is your Experience Cloud site domain. This URL points to the Salesforce OAuth 2.0 echo endpoint on your Experience Cloud site. The echo endpoint handles the code extraction step for headless login and headless registration, which saves you the work of writing and hosting your own code extraction endpoint. It returns the authorization code and other parameters from the 302 redirect as a JSON object that you can easily parse. 4. For Selected OAuth Scopes, add the **Manage user data via APIs (api)** and **Access unique user identifiers (openid)** scopes 5. For security, deselect these settings. - **Require Secret for Web Server Flow** - **Require Secret for Refresh Token Flow** Because this example is focused on single-page apps, which can’t keep information private, you don't want to send the app's consumer secret in HTTP requests. 6. Select **Enable Code and Credential Flow**. Enabling the flow exposes another setting to require user credentials in the POST body of your authorization request. Leave this setting deselected—again, your app can’t keep this information secret. 7. Save your external client app settings. Now that you have an OAuth-enabled external client app, you can get your consumer key and consumer secret. # Create the Restriction Rule Using the Metadata API Create a restriction rule that controls which tasks members of the Sales Team can access using the RestrictionRule Metadata API type. **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| |User Permissions Needed| | |-----------------------|--| |To create and manage restriction rules:|Manage Sharing| |To view restriction rules:|View Setup & Configuration AND View Restriction and Scoping Rules| You can create up to two restriction rules per object in Enterprise and Developer Editions and up to five restriction rules per object in Performance and Unlimited Editions. 1. Set up the `package.xml` manifest file and your directory. Example `package.xml` file: ```nolang * RestrictionRule 55.0 ``` Example directory: ```nolang myPackage/package.xml myPackage/restrictionRules myPackage/restrictionRules/Rule1.rule myPackage/restrictionRules/Rule2.rule ``` 2. Include all required fields. For more information, see the reference topic [RestrictionRule](https://developer.salesforce.com/docs/atlas.en-us.restriction_rules.meta/restriction_rules/meta_restrictionrule.htm). For our example, we set the fields as follows: ```nolang true Sales team can see only task records with specified record type Restrict Sales Team Record Type recordTypeId = '011xxxxxxxxxxxx' Task $User.ProfileId = '00exxxxxxxxxxxx' 1 ``` 3. Zip your directory and deploy your changes. For more information, see [Deploying and Retrieving Metadata](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/file_based.htm) in the Metadata API Developer Guide. 4. Note the ID returned for the newly created restriction rule for later reference. - **[Retrieve and Update Information](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-quickstart-metadata-calls.md)** Use the deploy() and retrieve() calls to move metadata (XML files) between a Salesforce organization and a local file system. # Data Access in Salesforce To meet your company’s security needs, it’s important to understand what data access means to your users and to you. ## Data Access: User’s Perspective If you put yourself in your users’ shoes, you won’t necessarily know or care how you’re getting access to records, but you might want to understand what having access means within the context of your organization. The following graph can help users visualize the different kinds of access that can be configured in Salesforce. ![User perspective diagram](https://developer.salesforce.com/media/salesforce-record-access-under-the-hood/data_access_user_perspective.png) For example, if a user has access to an account field, then they have access to both the account field and the account object itself. However, a specific account record, such as “Account A”, might not be accessible to that user due to additional access control applied via sharing rules or other tools. ## Data Access: Architect’s Perspective As an architect, you must both understand your user’s perspective and know how to grant users only the appropriate level of access to the data that they should be able to access. From an architect’s perspective, data access in Salesforce falls into two main categories: object-level access, which includes field-level access, and record-level access. Object-level access determines whether a user has access to a particular object, which fields they can see on that object, and which actions they can perform. You configure object level access on user profiles. - Restricting access - : The “Read,” “Create,” “Edit,” and “Delete” object permissions determine which actions a user can perform on any of the object's records to which they have access. Field-Level Security allows you to prevent certain users from seeing sensitive or confidential information contained in records they can see. - Opening up access - : The “View All” and “Modify All” object permissions give users access to all of an object’s records, regardless of record-level access settings. Record-level access (called “Sharing” in Salesforce) determines which records a user can see for a particular object, using the following tools: - Organization-wide defaults - Role hierarchy - Territory hierarchy - Sharing rules - Teams - Manual sharing - Programmatic sharing Because you have so many options for managing record-level access — and because some of these options are affected by organizational dependencies — determining which records users can access can quickly become complicated. Additionally, you might also be changing your sharing configuration frequently in response to new business requirements. This can trigger record access changes that ripple through your organization. These changes have an even greater impact in very large organizations, where it can take some time to recalculate access for a large number of users, and adjust the tables that record their access rights. For these reasons, it’s important to understand how Salesforce calculates and grants access at the database level. # Database Architecture **important:** Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations. Salesforce stores access grants in three types of tables. - Object Record Tables - : Tables that store the records of a specific object, and indicate which user, group, or queue owns each record. - Object Sharing Tables - : Tables that store the data that supports explicit and implicit grants. Most objects in your organization (to see them, from Setup, enter `Sharing Settings` in the Quick Find box, then select **Sharing Settings**) get their own Object Sharing table, unless any of the following conditions are also true: - The object is a detail in a master-detail relationship. In master-detail relationships, the Object Sharing table for the master object controls access to the detail object. - Both organization-wide default settings (internal and external) are Public Read/Write. - The object is of a type that doesn’t support Object Sharing tables, such as Activities or Files. These objects have their own access control mechanism. - Group Maintenance Tables - : Tables that store the data supporting group membership and inherited access grants. For example, if the Object Sharing table grants Bob explicit access to the Acme account record, Salesforce checks the Group Maintenance tables to see which users inherit record access from Bob and grants users access to the Acme record. These grants are established in advance when you create or change the group (or role, or territory) membership information. While Object Sharing tables store access grants to individuals and groups, Group Maintenance tables store the list of users or groups that belong to each group, indicating _group membership_. Both types of tables are used to determine a user’s access to data when they’re searching, querying, or pulling up a report or list view. When a user tries to retrieve one or more records, Salesforce generates a SQL statement that searches the Object Record table for records matching the user’s search string. If the record exists, Salesforce appends SQL to the statement that joins the Object Records table with the Object Sharing table, and the Object Sharing table with the Group Maintenance tables. Salesforce queries the joined tables for access grants that give the querying user access to the record. |When you join the following tables...|Salesforce matches on| |-------------------------------------|---------------------| |Object Record and Object Sharing|Record ID| |Object Sharing and Group Maintenance|User ID or group ID| This diagram illustrates that Salesforce matching process in sequence. ![Object Records table, Object Sharing table, and Group Maintenance tables with SQL joins.](https://developer.salesforce.com/media/salesforce-record-access-under-the-hood/joins.png) Salesforce returns only records that satisfy the entire statement, including its appended SQL. To satisfy the statement, the record must exist, and either the Object Sharing table or the Group Maintenance tables must grant access to the querying user. Even though both the Object Sharing and Group Maintenance tables provide access grants, the ways in which they provide those grants differ significantly. - Object Sharing tables simply store each access grant in separate rows called _sharing rows_, each of which grants a user or group access to a particular record. - Group Maintenance tables are more complex because a single group membership or inherited access grant can give several users and groups multiple ways to access a record. # Deferred Sharing Calculations Performing a large number of configuration changes can lead to long sharing rule evaluations or timeouts. To avoid these issues, you can suspend sharing calculations, specifically for sharing rules and group membership, then resume calculations during an organization's maintenance period. In addition to all the technical concerns a Salesforce admin must manage to perform a major realignment, they must also coordinate closely with the business to ensure that end users aren’t adversely affected when access rights are being adjusted. In an enterprise environment in which multiple systems are continually processing updates, it can be difficult to schedule an organization or sharing rule change that can take substantial time to complete. Deferred sharing calculations can help with increasing the predictability of these kinds of updates. Here’s how deferred sharing calculations works. 1. Based on requests from the business, a Salesforce admin identifies changes to the role hierarchy and group membership, or updates to sharing rules. 2. Given best estimates of the remaining overall work, the admin negotiates a maintenance window for completing the processing. **tip:** This window should be modeled in a full copy sandbox to get the best estimate possible. 3. Instead of processing each separate update and waiting for it to complete, the admin prepares all the information required to perform all updates ahead of the planned maintenance window. 4. At the start of the maintenance window, the admin uses the deferral feature to essentially “turn off” group membership or sharing rule calculations, and then makes all the desired changes at the same time. **note:** You can choose to only defer sharing rules calculations. However, if you defer group membership calculations, sharing rule calculations are automatically deferred. 5. After the changes have completed, the admin resumes group membership and sharing rule calculations. 6. At this point, the system is in a state that requires a full recalculation of all sharing rules for user access rights to be complete and accurate. The admin can recalculate sharing rules immediately or wait to start the process at a later time. After the sharing rule recalculation has completed, all the access changes take effect. When using the deferred sharing features, it's especially important to test the whole process in a full copy sandbox as you don’t want to defer sharing calculations for longer than is necessary. This practice helps benchmark how long the overall recalculation is likely to take in production and smooth out any kinks in orchestrating deferred sharing calculations. The full copy sandbox should mimic your current product environment as closely as possible. We recommend using a sandbox refreshed within the last 30 days that reflects all major changes. **note:** Deferring sharing calculations doesn’t defer the recalculation of some sharing changes in order to preserve data integrity. These calculations that can't be suspended can take significant time to process based on the org's settings and data volume. [Group membership locking](https://developer.salesforce.com/docs/platform/draes/guide/draes-group-membership-locking.md) can also still occur while sharing calculations are deferred. ## Who’s a Good Candidate for Deferred Sharing? There are two main criteria for determining whether deferred sharing calculation is the right tool for your organization: the size and complexity of your realignment activities, and the flexibility you have to arrange a maintenance window with your customers. If you find that organizational changes and sharing rule updates typically complete quickly enough to be scheduled into the workday and weekend downtimes in your use of the service, you’re unlikely to benefit substantially from this feature. On the other hand, if you’re able to negotiate downtime with your business customers and have been struggling to complete updates in a timely fashion, deferred sharing can be a good solution to your problem. # Delete Field History and Field Audit Trail Data Use Apex or SOAP to delete field history and field history archive data. To delete field history and audit trail data, the user permissions Delete From Field History and Delete From Field History Archive must be enabled through a permission set or a user profile. The org preferences to enable these permissions, Delete From Field History and Delete From Field History Archive, are located in **Setup | User Interface**. Delete field history data, such as AccountHistory, by passing in a list of ID values as strings using the Apex or SOAP `delete()` method. The Apex `delete()` method also works with a list of sObjects with the Id field populated. Delete field history archive data from the FieldHistoryArchive big object using the SOAP `deleteByExample()` or Apex `Database.deleteImmediate()` methods. The sObject acts like a template. All rows that match the sObject’s fields and values are deleted. You can only specify fields that are part of the big object’s index. You must specify all fields in the index. You can’t include a partially specified index or non-indexed field, and wildcards are not supported. Sample for deleting AccountHistory: ```apex List ah = new List(); ah.addAll( [ SELECT Id FROM AccountHistory WHERE AccountId = '001d000000Ky3xIAB' and CreatedDate = YESTERDAY ] ); Database.delete(ah); ``` Samples for deleting from FieldHistoryArchive: ```apex List fha = new List(); fha.addAll([SELECT FieldHistoryType, ParentId, CreatedDate, HistoryId FROM FieldHistoryArchive WHERE FieldHistoryType = 'Account' AND ParentId = '001d000000Ky3xIAB' AND CreatedDate = '2017-11-28T19:13:36.000z' AND HistoryId = '017D000000ESURXIA5']); Database.deleteImmediate(fha); ``` Alternatively, delete field history archive data with the SOAP call `deleteByExample()`. Declare an sObject containing the fields and values in the `FieldHistoryArchive` big object to delete. This example deletes all rows that meet the specified criteria. ```java public static void main(String[] args) { try{ //Create two sObjects to delete and place them in an array of sObjects to pass to the delete method FieldHistoryArchive[] sObjectsToDelete = new FieldHistoryArchive[2]; FieldHistoryArchive fha_1 = new FieldHistoryArchive(); fha_1.setFieldHistoryType("Account"); fha_1.setParentId("001d000000Ky3xIAB"); Calendar dt = connection.getServerTimestamp().getTimestamp(); dt.add(Calendar.DAY_OF_MONTH, -7); fha_1.setCreatedDate(dt); fha_1.setHistoryId("017D000000ESURXIA5"); sObjectsToDelete[0] = fha_1; FieldHistoryArchive fha_2 = new FieldHistoryArchive(); fha_2.setFieldHistoryType("Account"); fha_2.setParentId("001d000000Ky3xIAB"); fha_2.setCreatedDate(dt); fha_2.setHistoryId("017D000000ESURXIA5"); sObjectsToDelete[1] = fha_2; DeleteByExampleResult[] result = connection.deleteByExample(sObjectsToDelete); } catch (ConnectionException ce) { ce.printStackTrace(); } } ``` **note:** Repeating a successful `deleteByExample()` operation produces a success result, even if the rows have already been deleted. **See Also** - [`deleteByExample()`](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_calls_deletebyexample.htm) # Designing Record Access for Enterprise Scale Previous PDF Versions | API Version | Release | PDF | | ----------- | ---------- | --------------------------------------------------------------------------------------------------------- | | 68.0 | Winter '27 | [Download PDF](https://resources.docs.salesforce.com/264/latest/en-us/sfdc/pdf/draes.pdf) | | 67.0 | Summer '26 | [Download PDF](https://resources.docs.salesforce.com/262/latest/en-us/sfdc/pdf/draes.pdf) | | 66.0 | Spring '26 | [Download PDF](https://resources.docs.salesforce.com/260/latest/en-us/sfdc/pdf/draes.pdf) | | 65.0 | Winter '26 | [Download PDF](https://resources.docs.salesforce.com/258/latest/en-us/sfdc/pdf/draes.pdf) | # Display a Branch Location’s Records by Default This scoping rule displays task records associated with a particular bank branch location by default. A custom field called Branch__c stores the bank’s branch locations. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance**,**Unlimited**, and **Developer** editions.| ## Tooling API ```nolang { "FullName":"Task scoping rule on Bank Branch 1", "Metadata": { "active":true, "description":"View tasks for Bank Branch 1.", "enforcementType":"Scoping", "masterLabel":"SR for Bank Branch 1", "recordFilter":"Branch__c = $User.Branch__c", "targetEntity":"Task", "userCriteria":"$User.UserRoleId = '00Exxxxxxxxxxxx'", "version":1 } } ``` ## Metadata API ```nolang true View tasks for Bank Branch 1. Scoping SR for Bank Branch 1 Branch__c = $User.Branch__c Task $User.UserRoleId = '00Exxxxxxxxxxxx' 1 ``` # Display a Department’s Records by Default This scoping rule displays contact records associated with a particular department by default for a user who works on them. The rule dynamically matches the contact owner's department with the current user's department. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance**,**Unlimited**, and **Developer** editions.| ## Tooling API ```nolang { "FullName":"Department A contact scoping rule", "Metadata": { "active":true, "description":"View contacts from Department A.", "enforcementType":"Scoping", "masterLabel":"SR for Department A", "recordFilter":"Department=$User.Department", "targetEntity":"Contact", "userCriteria":"$User.UserRoleId = '00Exxxxxxxxxxxx'", "version":1 } } ``` ## Metadata API ```nolang true View tasks contacts from Department A. Scoping SR for Department A contacts Department=$User.Department Contact $User.UserRoleId = '00Exxxxxxxxxxxx' 1 ``` # Display a Division’s Tasks by Default This scoping rule displays records associated with a particular division by default for a user. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance**,**Unlimited**, and **Developer** editions.| ## Tooling API ```nolang { "FullName":"Task scoping rule on current user’s Division", "Metadata": { "active":true, "description":"View tasks in the current user’s Division.", "enforcementType":"Scoping", "masterLabel":"SR for Divisions", "recordFilter":"Division=$User.Division", "targetEntity":"Task", "userCriteria":"$User.ProfileId = '00exxxxxxxxxxxx'", "version":1 } } ``` ## Metadata API ```nolang true View tasks in the current user’s Division. Scoping SR for Divisions Division=$User.Division Task $User.ProfileId = '00exxxxxxxxxxxx' 1 ``` # Enable Digital Experiences and Create Your Site Enabling digital experiences is the first step to creating your Experience Cloud site. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From Setup, in the Quick Find box, enter `Digital`, and then select **Digital Experiences | Settings**. 2. Select **Enable Digital Experiences**. 3. If enhanced domains are enabled in your org, you see your digital experiences domain name. Otherwise, enter a name, and then to make sure that it isn’t being used, click **Check Availability**. 4. Save your changes. 5. From Setup, in the Quick Find box, enter `Sites`, and then select **All Sites**. 6. Click **New**. 7. Select a site template. For this example, select **Build Your Own (LWR)**. 8. Click **Get Started**. 9. **note:** We recommend that you don’t provide a URL suffix for your site. That way, you’re not required to remember it every time you need the Experience Cloud domain. Enter a name for the site. For this example, name the site `Headless Demo`. 10. Click **Create**. Your site opens in Experience Workspaces. Keep your site open in Experience Workspaces for the next step. # Enable the Authorization Code and Credentials Flow The Authorization Code and Credentials Flow is the foundation of headless login and headless registration. Enable this flow at an org-wide level. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From Setup, in the Quick Find box, enter `OAuth`, and then select **OAuth and OpenID Connect Settings**. 2. Turn on **Allow Authorization Code and Credentials Flows**. You receive a warning that changing this setting can break your integrations. 3. To accept the warning, click **OK**. With this flow enabled, you can access settings to turn it on for a specific external client app. ## Example ![Toggle to turn on the Authorization Code and Credentials Flow](https://developer.salesforce.com/media/headless-identity-impl-guide/authcodecreds_toggle.png) # Example Scenarios Refer to these sample restriction rules that fulfill different access requirements. To review how these example scenarios are built in Salesforce, see Restriction Rule Example Scenarios in Salesforce Help. |Required Editions| |-----------------| |Available in: Lightning Experience in **Enterprise**, **Performance**, **Unlimited**, and **Developer** Editions| - **[Allow Users to See Only Specified Record Type](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-record-type.md)** This restriction rule allows the designated users to see only contracts that have a specified record type. - **[Allow Users to See Only Records That They Own](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-records-owned.md)** This restriction rule allows users with the designated profile to see only the tasks that they own. - **[Allow Users to See Only Records Owned by Same Role](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-role.md)** This restriction rule allows active users to see only the events owned by users that have the same role. You can use dot notation to traverse object and field relationships in the `recordFilter` field. - **[Allow Users to See Only Records Owned by Same Profile](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-profile.md)** This restriction rule allows active users to see only the events owned by users that have the same profile. You can use dot notation to traverse object and field relationships in the `recordFilter` field. - **[Allow Users to See Records Based on a Custom Field](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-custom-field.md)** This restriction rule allows high-volume users to see only the contracts where the user's department matches the contract's department. This rule uses a custom field, `Department__c`, that must have the appropriate value set through Apex, Process Builder, workflows, or flows. - **[Allow Users to See an External Object's Records](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-external-object.md)** This restriction rule allows active Salesforce users to see the records of an external object called Purchase Order. The rule uses a field called IsClosed on Purchase Order records in its record criteria. - **[Provide User Access With Multiple String or ID Values in Record Criteria](https://developer.salesforce.com/docs/platform/restriction-rules/guide/restriction-rules-example-multiple-values.md)** This restriction rule allows active users to see records whose Name__c field matches the rule’s record criteria value. The record criteria contains strings separated by a comma. ID values are also supported. Double-quotes specify that the value inside the quotes isn’t considered a delimiter. **See Also** - [*Salesforce Help*: Restriction Rule Example Scenarios](https://help.salesforce.com/s/articleView?id=platform.security_restriction_rule_examples.htm&type=5) # Example Scenarios These sample scoping rules provide relevant records to users. |Required Editions| |-----------------| |Available in: Lightning Experience in **Performance**,**Unlimited**, and **Developer** editions.| - **[Display a Branch Location’s Records by Default](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/guide/scoping-rules-example-location.md)** This scoping rule displays task records associated with a particular bank branch location by default. A custom field called Branch__c stores the bank’s branch locations. - **[Display a Department’s Records by Default](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/guide/scoping-rules-example-dept.md)** This scoping rule displays contact records associated with a particular department by default for a user who works on them. The rule dynamically matches the contact owner's department with the current user's department. - **[Display a Division’s Tasks by Default](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/guide/scoping-rules-example-division.md)** This scoping rule displays records associated with a particular division by default for a user. - **[Scope Records Using Multiple String or ID Values in Record Criteria](https://developer.salesforce.com/docs/platform/scoping-rules-dev-guide/guide/scoping-rules-example-multiple-values.md)** This scoping rule allows active users to scope the records they see to records whose Name__c field matches the rule’s record criteria value. The record criteria contains strings separated by a comma. ID values are also supported. Double-quotes specify that the value inside the quotes isn’t considered a delimiter. # Examples Here are some examples of field history workflows. ## Set Data Retention Policy for Field History This example demonstrates how to set a field history data retention policy using Metadata API. By default, field history data is not automatically deleted. Edit the metadata only if you want to override the default policy values of 18 months of production storage. Setting a data retention policy involves creating a metadata package and deploying it. The package consists of a `.zip` file and a project manifest that lists the objects and the API version to use. The `.zip` file contains an `objects` folder with the XML that defines each object’s retention policy. **note:** The first copy writes the entire field history that’s defined by your policy to archive storage and takes a long time. Subsequent copies transfer only the changes since the last copy, and are faster. 1. Define a field history data retention policy for each object. The policy specifies the number of months that you want to maintain field history in Salesforce. The following sample file defines a policy of archiving the object after six months. ```xml 6 5 My field history retention ... ``` The file name determines the object to which the policy is applied. For example, to apply the preceding policy to the Account object, save the file as `Account.object`. For existing custom objects, the file is also named after the custom object. For example: `myObject__c.object`. 2. Create the project manifest, which is an XML file that’s called `package.xml`. The following sample file lists several objects for which data retention policy is to be applied. With this manifest file, you expect the objects folder to contain five files: `Account.object`, `Case.object`, and so on. ```xml Account Case Contact Lead Opportunity CustomObject 45.0 ``` 3. Create the `.zip` file and use the [`deploy()`](https://developer.salesforce.com/docs/atlas.en-us.264.0.api_meta.meta/api_meta/meta_deploy.htm) function to deploy your changes to your production environment. For more information, see the [Metadata API Guide](https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/). **note:** This feature doesn’t support deployment from sandbox to production environments. That's it! Your field history retention policy goes into effect according to the time periods that you set. ## Create a Custom Object and Set Field History Retention Policy at the Same Time You can use Metadata API to create a custom object and set retention policy at the same time. Specify the minimum required fields when creating a custom object. This sample XML creates an object and sets field history retention policy. ```xml Deployed true just a test object with one field for eclipse ide testing 3 10 1 Transaction Line History Comments__c add your comments about this object here This field contains comments made about this object 32000 true LongTextArea 30 Text MyFirstObjects ReadWrite ``` Set `trackHistory` to `true` on the fields that you want to track and `false` on the other fields. ## Update Data Retention Policy for Field History If a field history data retention policy is already defined on an object, you can update the policy by specifying a new value of `HistoryRetentionPolicy` in the metadata for that object. When you deploy the metadata changes, the new policy overwrites the previous one. **note:** To check the current data retention policy for any object, retrieve its metadata using Metadata API and look up the value of `HistoryRetentionPolicy`. ## Query Archived Data You can retrieve archived data by making SOQL queries on the `FieldHistoryArchive` object. You can filter on the `FieldHistoryType`, `ParentId`, and `CreatedDate` fields, as long as you specify them in that order. For example: ```soql SELECT ParentId, FieldHistoryType, Field, Id, NewValue, OldValue FROM FieldHistoryArchive WHERE FieldHistoryType = 'Account' AND ParentId='001D000000INjVe' ``` # Field Audit Trail Field Audit Trail extends the capabilities of Field History Tracking, so you can keep field history data indefinitely. Use Field Audit Trail to maintain a comprehensive historical record of field changes (up to 200 tracked fields per object) and to ensure data integrity and compliance with industry standards. Field History Tracking data and Field Audit Trail data don’t count against your data storage limits. || |-| |Available in: Lightning Experience| |Available in: **Enterprise**, **Performance**, and **Unlimited** Editions with the purchase of Salesforce Shield or Field Audit Trail licenses.| **note:** This content relates to Field Audit Trail. Read about implementing field audit trail using Shield Extension in [Own from Salesforce](https://help.salesforce.com/s/articleView?id=platform.own_from_salesforce.htm&type=5). To capture field history data by using Field Audit Trail, first enable Field History Tracking for objects and for specific fields on those objects. When you enable Field History Tracking, changes that occur in tracked fields are recorded in the object’s related history object (for example, when you make changes to an account field, those changes are saved in the AccountHistory object) and these changes appear on the object’s history related list. After you define and deploy a Field Audit Trail policy, Salesforce copies the field history from the object’s history list to the FieldHistoryArchive big object. The first copy takes a long time, because it saves all the field history defined by your policy to the archive storage. Subsequent copies are faster, because they transfer only the changes since the last copy. | |Field History Tracking|Field Audit Trail| |--|----------------------|-----------------| |Fields Tracked|Up to 20 per object|Up to 200 per object| |Retention Period|18-24 months|Until deleted| |Storage|The object's history object|FieldHistoryArchive| |Access|UI and API|API only| The FieldHistoryArchive big object stores all archived field history changes. For each tracked object, you can also define a field history data retention policy in the HistoryRetentionPolicy object that specifies the number of months that you want to retain field history in your system. You can include these policies in both managed and unmanaged packages. You can define field history retention policies for these objects: - Accounts, including Person Accounts - Assets - Authorization Form Consent - Campaigns - Cases - Communication Subscription Consent - Contacts - Contact Point Consent - Contact Point Type Consent - Contracts - Contract Line Items - Crisis - Employee - Employee Crisis Assessment - Entitlements - Individuals - Internal Organization Unit - Leads - Net Zero Cloud objects - Opportunities - Orders - Order Products - Party Consent - Price Books - Price Book Entries - Products - Service Appointments - Service Contracts - Solutions - Work Orders - Work Order Line Items - Custom objects with field history tracking enabled You can’t track these fields: - Formula, roll-up summary, or auto-number fields - Created By and Last Modified By - The Expected Revenue field on opportunities - The Master Solution Title or the Master Solution Details fields on solutions - Long text fields - Multiselect fields You use Metadata API to define and deploy your field history retention policy. After you deploy your field history retention policy to a production environment, you can query Field Audit Trail entries stored on the FieldHistoryArchive big object. See [Query Batches of Archived Field History Data](https://help.salesforce.com/s/articleView?id=xcloud.field_audit_trail_query.htm&type=5). **note:** When Field Audit Trail is turned on, Salesforce relates HistoryRetentionPolicy automatically to the supported objects. By default, Salesforce archives data after 18 months in production and after one month in sandboxes, and stores all archived data until you delete it. Salesforce doesn’t include the default retention policy when you retrieve the object’s definition through Metadata API. Salesforce retrieves only custom retention policies with the object definition. When you delete field history archive data, use REST API, SOAP API, or Tooling API. If you delete a record in your production data, the delete cascades to the related history tracking records, but Salesforce doesn’t delete the history copied into the FieldHistoryArchive big object. For information about deleting data in FieldHistoryArchive, see [Delete Field History and Field Audit Trail Data](https://developer.salesforce.com/docs/atlas.en-us.field_history_retention.meta/field_history_retention/field_history_delete.htm). For information about enabling Field Audit Trail, contact your Salesforce representative. **tip:** If you turn on Platform Encryption, previously archived data remains unencrypted. For example, let's say that your organization uses Field Audit Trail to define a data history retention policy for an account field, such as the phone number field. After you turn on Platform Encryption, Salesforce encrypts the phone number data in the account, as well as new phone number records and previous updates stored in the Account History related list. But phone number history data that's already archived in the FieldHistoryArchive big object remains stored without encryption. To encrypt previously archived data, contact Salesforce to encrypt and rearchive the stored field history data, and then delete the unencrypted archive. # Field Audit Trail Implementation Guide Previous PDF Versions | API Version | Release | PDF | | ----------- | ---------- | ----------------------------------------------------------------------------------------------------------- | | 68.0 | Winter '27 | [Download PDF](https://resources.docs.salesforce.com/264/latest/en-us/sfdc/pdf/field_history_retention.pdf) | | 67.0 | Summer '26 | [Download PDF](https://resources.docs.salesforce.com/262/latest/en-us/sfdc/pdf/field_history_retention.pdf) | | 66.0 | Spring '26 | [Download PDF](https://resources.docs.salesforce.com/260/latest/en-us/sfdc/pdf/field_history_retention.pdf) | | 65.0 | Winter '26 | [Download PDF](https://resources.docs.salesforce.com/258/latest/en-us/sfdc/pdf/field_history_retention.pdf) | # FieldHistoryArchive Represents field history values for all objects that retain field history. `FieldHistoryArchive` is a big object, available only to users with the “Retain Field History” permission. This object is available in API version 29.0 and later. Each instance of the `FieldHistoryArchive` object represents a single change in the value of a field. `FieldHistoryArchive` stores history for both standard and custom fields. The `Field` field returns the name of the field unless the parent field or object is deleted, in which case it returns the field ID. You can use the ID to retrieve the old field and object name from the `FieldNameAfterArchival` and `ParentNameAfterArchival` fields, respectively. ## Supported Calls `describeSObjects()`, `query()` ## Fields ### ArchiveFieldName - **Type**: string - **Properties**: Nillable - **Description**: The name of the field at the time the data was archived. If the field name changed, the name is sometimes not the same for all records related to a single field. ### ArchiveParentName - **Type**: string - **Properties**: Nillable - **Description**: The name of the parent object at the time the data was archived. If the object name changed, the name is sometimes not the same for all records related to a single field. ### ArchiveParentType - **Type**: string - **Properties**: Nillable - **Description**: The type of the field at the time the data was archived. If the field type changed, the type is sometimes not the same for all records related to a single field. ### ArchiveTimestamp - **Type**: dateTime - **Properties**: Nillable - **Description**: The date and time at which the data was archived. ### CreatedById - **Type**: reference - **Properties**: Nillable - **Description**: The user ID of the user who created the original record. ### CreatedDate - **Type**: dateTime - **Properties**: Nillable, Sort - **Description**: The date and time at which the original record was created. ### Field - **Type**: picklist - **Properties**: Restricted picklist - **Description**: The name of the field that was changed. If the field is deleted from the parent object, the `Field` field contains the field ID instead. ### FieldHistoryType - **Type**: picklist - **Properties**: Filter, Nillable, Restricted picklist, Sort - **Description**: The name of the object that contains the field history. - **Possible values**: - `Account` - `Article` - `Asset` - `AuthorizationFormConsent` – Available in version 58.0 and later. - `Case` - `CommSubscriptionConsent` – Available in version 58.0 and later. - `Contact` - `ContactPointConsent` – Available in version 58.0 and later. - `ContactPointTypeConsent` – Available in version 58.0 and later. - `Contract` - `ContractLineItem` - `Crisis` - `Employee` - `EmployeeCrisisAssessment` - `Entitlement` - `Individual` - `InternalOrganizationUnit` - `Knowledge` - `Lead` - `Opportunity` - `Order` - `OrderItem` - `PartyConsent` – Available in version 58.0 and later. - `Pricebook2` - `PricebookEntry` - `Product2` - `ServiceAppointment` - `ServiceContract` - `Solution` - `WorkOrder` - `WorkOrderLineItem` ### HistoryId - **Type**: reference - **Properties**: Filter, Sort - **Description**: The ID of the relevant history object (for example, AccountHistory). This field is available in versions 42.0 and later. ### Id - **Type**: ID - **Properties**: Defaulted on create, Filter, idLookup - **Description**: The ID of the archived record. It’s useful to have a field’s ID for fields that you’ve deleted. (Field names aren’t retained in history when you delete fields from Salesforce.) ### NewValue - **Type**: anyType - **Properties**: Nillable - **Description**: The new value of the modified field. ### OldValue - **Type**: anyType - **Properties**: Nillable - **Description**: The previous value of the modified field. ### ParentId - **Type**: reference - **Properties**: Filter, Sort - **Description**: The ID of the object that contains the field (the parent object). ## Usage When sorting fields, order them as follows: 1. `FieldHistoryType` `ASC` 2. `ParentID` `ASC` 3. `CreatedDate` `DESC` # Get Started with Named Credentials **tip:** For more information about named credentials, including how to create and manage named credentials in the UI, see the [Named Credentials](https://help.salesforce.com/s/articleView?id=xcloud.named_credentials_about.htm) help documentation. ## Introduction A named credential specifies the URL of a callout endpoint and its required authentication parameters in one definition. To simplify the setup of authenticated callouts, specify a named credential instead of a URL as the callout endpoint. **important:** IMPORTANT In Winter ’23, Salesforce introduced an improved named credential that is extensible and customizable. We strongly recommend that you use this preferred credential instead of legacy named credentials. For information on extensible, customizable named credentials, see [Named Credentials and External Credentials](https://help.salesforce.com/s/articleView?id=xcloud.nc_named_creds_and_ext_creds.htm). Legacy named credentials are deprecated and will be discontinued in a future release. Named credentials are supported in these types of callout definitions. - Apex callouts - External data sources - External Services Create an external credential to specify an authentication protocol and permission set or profile to use when authenticating to an external system. Add custom headers to named and external credentials to cover more use cases and security requirements. ## Parts of the Named Credential Schema ![The Named Credentials Schema](https://developer.salesforce.com/docs/media/240_named_credentials_chime_example_01b.png) Named credentials and external credentials simplify and enable greater reuse of setup for secure and authenticated callouts. - A named credential specifies a callout endpoint and an HTTP transport protocol. - An external credential’s authentication protocol and user credentials authenticate the caller. The same external credential can be used by more than one named credential. - External credential principals map to user permissions to authorize them to make callouts. - User external credentials store encrypted tokens. ## Available Named Credential Developer Guides These guides are available. - [Named Credentials Developer Guide for OAuth](https://developer.salesforce.com/docs/platform/named-credentials/guide/nc-oauth-dev-guide.md) Explains how to create, edit, and delete a credential that uses OAuth authentication, and how to refresh an OAuth token. - [Named Credentials Packaging Guide](https://developer.salesforce.com/docs/platform/named-credentials/guide/nc-packaging-dev-guide.md) Explains how to package named credentials, and how to use the Connect API to populate sensitive access tokens that can’t be packaged. # Get Started with Record-Level Access: Under the Hood If you’re a Salesforce architect, you already know your overarching goal for data security: Understand your company’s enterprise security model, then build a data access model that reflects it in your Salesforce organization. You have a rich set of Salesforce tools at your disposal for managing that data access, but you don’t manage it at just one level. Instead, you must carefully consider when and how to control access at the object, record, and field levels. In this paper, you can find a brief overview of these different levels of data access, and under the hood, table-level views of record-level access. After reading _Record-Level Access: Under the Hood_, you should feel better prepared to give the right users the right access to the right records at the right time — and do so in the fastest time possible. ## Audience This paper is intended for expert architects working on Salesforce implementations that have complex record access requirements or require large-scale realignments of sales organizations. ## Assumptions This paper assumes that you are an expert in Salesforce administration and security, and in SQL and relational database concepts. # Get Started with Salesforce Connect Adapter for GraphQL GraphQL is a standard query language that allows applications to flexibly access required data and provides a modern way to integrate applications. Unlike traditional REST APIs, GraphQL is more efficient because it provides granularity for client apps to specify the fields that they need. The Salesforce Connect adapter for GraphQL includes special capabilities for Amazon AppSync and provides seamless access to Amazon RDS. Amazon AppSync hosts the GraphQL endpoint to accept and validate GraphQL queries. The GraphQL resolver translates GraphQL operations into SQL statements and executes them against an Amazon RDS-hosted database. You can then create an external data source with the endpoint URL of the AppSync GraphQL API and provide access to AWS data source from Salesforce. ![AmazonRDS](https://developer.salesforce.com/docs/media/RDS-AppSync-SFConnect.png) For more information about Salesforce Connect adapter for GraphQL, see [Access External Data with the Salesforce Connect Adapter for GraphQL](https://help.salesforce.com/s/articleView?id=sf.salesforce_connect_graphQL.htm&type=5) help documentation. # Get Your Consumer Key When you create an external client app, it generates a consumer key, also known as a client ID. This value allows Salesforce to identify your third-party app during headless identity flows. Learn how to get your consumer key so that you can use it when configuring headless flows. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. If you’re not on the external client app detail page from the previous step, go to your app page. 1. From Setup, in the Quick Find box, enter `External`, and then select **External Client App Manager**. 2. Find your app and click its name. On the app detail page, click the Settings tab. 2. Expand the OAuth Settings section and click **Consumer Key and Secret**. Salesforce prompts you to verify your identity with one of your registered methods. After you verify your identity, you can see your consumer key. # Getting User Info JavaScript Examples When you get an access token via headless registration or headless login, you can retrieve user information with a request to the User Info endpoint. Use this example to understand how. In this example, the access token is passed in an Authorization Bearer header. ```nolang function getUserInfo(accessToken) { client = new XMLHttpRequest(); client.open("GET", expDomain + userInfoURI, true); client.setRequestHeader("Content-Type", "application/json"); client.setRequestHeader("Authorization", 'Bearer ' + accessToken); client.send(); client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { //User Info response console.log(client.response); } else { console.log(client.response) onError("An Error Occured during Forgot Password Step: " + forgotPasswordProcessStep, client.response); } } } } ``` # Group Maintenance Tables Sharing rows grant access to users and groups, but the data that specifies who belongs to each group resides in the Group Maintenance tables. These tables store membership data for every Salesforce group, including _system-defined groups_. System-defined groups are groups of users that Salesforce creates and manages internally to support various features and behaviors, such as queues. This type of management lets the data that supports queues and personal or public groups coexist in the same database tables, and unifies how Salesforce manages the data. For example, Salesforce can grant record access to a queue the same way it grants record access to a public group. Salesforce also uses system-defined groups to implement hierarchies. During recalculation, Salesforce creates up to three system-defined groups (_Role_, _RoleAndSubordinates_, and _RoleAndSubordinatesInternal_) depending on if digital experiences is enabled. |Group|Consists of|Purpose| |-----|-----------|-------| |Role|Users assigned to any of the following.
  • A specific role
  • One of its manager roles
|Used to give managers access to their subordinates’ records| |RoleAndSubordinates|Users assigned to any of the following.
  • A specific role
  • One of its manager roles
  • One of its subordinate roles
|Used when an organization defines a rule that shares a set of records with:
  • A particular role
  • Its subordinates
Only available when digital experiences is enabled for your org and Experience Cloud site users are created with external account roles other than a shared person account role.| |RoleAndSubordinatesInternal|Users assigned to any of the following.
  • A specific role
  • One of its manager roles
  • One of its subordinate roles, excluding Portal roles
|Used when an organization defines a rule that shares a set of records with:
  • A particular role
  • Its subordinates, excluding Portal roles
| All three group types have: - Indirect members, who inherit record access from the group’s direct members and are assigned to manager roles - Direct members, who are defined according to their group type - In Role groups, direct members are those members assigned to the role the group represents. - In RoleAndSubordinates groups, direct members are those members assigned to the role the group represents or one of its subordinate roles - In RoleAndSubordinatesInternal groups, direct members are those members assigned to the role the group represents, or one of its non-portal subordinate roles. For example, consider the following simple role hierarchy. ![Hierarchy](https://developer.salesforce.com/media/salesforce-record-access-under-the-hood/hierarchy_simple.png) To support the record access inheritance this hierarchy establishes, Salesforce defines the following Role and RoleAndSubordinates groups, resulting in eight total groups. ![Groups](https://developer.salesforce.com/media/salesforce-record-access-under-the-hood/groups.png) By scanning the Role groups, Salesforce can quickly identify the indirect members who inherit record access from users at that role. For example, to see which users inherit record access from Bob in the role hierarchy, Salesforce simply searches for Role groups that have Bob as a direct member (the East Sales Rep Role group), and finds all the indirect members in those groups (Marc and Maria). Likewise, by scanning the RoleAndSubordinates groups, Salesforce can quickly see which users receive access through role and subordinate sharing rules. For example, if a rule shares a set of records with users in the Sales Executive role and their subordinates, Salesforce can identify those users by scanning the Sales Executive RoleAndSubordinates group. System-defined groups support Territory Management in a similar way. For each territory, Salesforce creates a: - Territory group, in which users who are assigned to the territory are direct members, while users assigned to territories higher in the hierarchy are indirect members - TerritoryAndSubordinates group, in which users who are assigned to that territory or territories lower in the hierarchy are direct members, while users assigned to territories higher in that branch are indirect members Without system-defined groups, every record access attempt would have to send Salesforce traversing every branch in its hierarchies, jumping back and forth between tables of user data and tables of hierarchical data. Salesforce would also have to use disparate processes to handle what are all essentially collections of users. Users can’t modify system-defined groups through the user interface or API in the ways that they can personal and public groups; however, modifying a queue or hierarchy causes Salesforce to recalculate its associated system-defined groups accordingly. Thus, the size and complexity of an organization’s queues and hierarchies directly affect the duration of record access calculations. # Group Membership Locking When updating the role hierarchy or group membership in Setup or through the API, customers can occasionally receive a “could not acquire lock” or "Group membership operation already in progress" error and have to repeat the operation. This error occurs because the sharing system locks the tables holding group membership information during updates to prevent incompatible simultaneous updates or timing issues, both of which could lead to inaccurate data about users’ access rights. Typically, these locks are held only very briefly, so most customers never see a lock conflict error. In some scenarios—such as a change in role triggering a sharing rule recalculation—locks can be held for a longer time, and conflicts can occur. Customers who experience these locking errors are typically executing large-scale data loads or integrations with other internal systems that are making changes to role and group structure, user assignments to roles and groups, or both. When these processes are running—and a Salesforce admin tries to change a user’s role, or the customer tries to provision a new external user—one of these simultaneous operations might be unable to secure the lock it requires. The most likely time for this failure to occur is during periodic organizational realignment events, such as end-of-year or end-of-quarter processing, where many account assignments and user roles are changing. Customers can lessen the chance of locking errors by: - Scheduling separate group maintenance processes carefully so they don’t overlap - Implementing retry logic in integrations and other automated group maintenance processes to recover from a failure to acquire a lock - Trying serial processing if using parallel processing leads to locking errors **note:** You can also receive locking errors if you’re updating the role hierarchy or group membership while running other deployments that also update group membership information or have Apex tests that do so. If you receive locking errors, wait for the deployment operation or Apex tests to finish. By default, granular locking is enabled, which allows some group maintenance operations to proceed simultaneously if there’s no hierarchical or other relationship between the roles or groups involved in the updates. Admins can adjust their maintenance processes and integration code to take advantage of this limited concurrency to process large-scale updates faster, all while still avoiding locking errors. The key advantages of granular locking are that: - Groups that are in separate hierarchies can be manipulated concurrently. - Public groups and roles that don't include territories aren't blocked by territory operations. - Users can be added concurrently to territories and public groups. - User provisioning can occur in parallel. - External user creation requires locks only if new external roles are being created. - Provisioning new external users in existing accounts occurs concurrently. - A single-long running process, such as a role delete, blocks only a small subset of operations. This table lists all the operations that can occur in parallel. If a specific grouping of operations isn’t listed in the table, then concurrent processing isn’t supported and group membership locking can occur. Note that certain operations, such as reparenting (moving roles within the role hierarchy), still block almost all other group updates. |Group Operation|Can be Performed Concurrently with...| |---------------|-------------------------------------| |Role creation|
  • User role change:sup[1]
  • Territory reparenting
  • Territory deletion
  • Territory creation
  • Removal of user from territory
  • Addition of user to territory
  • User provisioning:sup[2]
| |Role deletion|
  • Territory reparenting
  • Territory deletion
  • Territory insertion
  • Removal of user from territory
  • Addition of user to territory
| |Role reparenting (includes change of customer or partner account owner)|Territory creation| |Adding user to territory|
  • Role deletion
  • Role insertion
  • Territory creation
  • Addition of user to territory
  • User provisioning:sup[3]
| |Removing user from territory|
  • Role deletion
  • Role insertion
  • Territory creation
  • User provisioning:sup[3]
| |Territory reparenting|
  • Role deletion
  • Role insertion
  • User provisioning:sup[3]
| |Territory deletion|
  • Role deletion
  • Role insertion
  • User provisioning:sup[3]
| |Territory creation|
  • Role reparenting
  • Role deletion
  • Role insertion
  • User role change:sup[1]
  • Addition of user to territory
  • Removal of user from territory
  • User provisioning:sup[3]
| |Provisioning internal user with an existing role|
  • Role insertion
  • User role change:sup[1]
  • Territory reparenting
  • Territory deletion
  • Territory creation
  • Removal of user from territory
  • Addition of user to territory
  • User provisioning:sup[3]
| |Changing user role (User must not own any customer or partner accounts.)|
  • Role insertion
  • Territory insertion
  • User provisioning:sup[3]
| |Provisioning first customer or partner user under an account|
  • User role change:sup[1]
  • Territory reparenting
  • Territory deletion
  • Territory creation
  • Removal of user from territory
  • Addition of user to territory
  • User provisioning:sup[2]
| |Creating second customer or partner user under an account|
  • Role insertion
  • User role change:sup[1]
  • Territory reparenting
  • Territory deletion
  • Territory creation
  • Removal of user from territory
  • Addition of user to territory
  • User provisioning:sup[3]
| |Provisioning high-volume Experience Cloud site user|Any group membership operation| |Changing customer or partner account owner|Territory creation| |Changing role of a user who owns a customer or partner account|Territory creation| :sup[1] The user must not own any customer or partner accounts. :sup[2] Provisioning standard user or external user in an existing role :sup[3] Provisioning any standard or external user, including the first customer or partner user under an account # Group Membership Operations and Sharing Recalculation The Salesforce role hierarchy, public groups, and territories are closely connected to sharing rules and other security features. Because of these relationships, seemingly simple changes to groups and group membership can sometimes involve substantial recalculations of users' access rights. For example, when an administrator moves a user from one branch of the hierarchy to another, Salesforce performs these actions to ensure that other users have correct access to data owned by that relocated user. - If the user: - Is the first member in his or her new role to own any data, Salesforce adds or removes access to the user’s data for people who are above the user’s new or old role in the hierarchy. - Owns any customer or partner accounts, Salesforce removes any child customer or partner account roles from the user’s original role and adds them as children to the user’s new role. **note:** - Salesforce also adjusts implicit shares that provide access in the hierarchy to records owned by or shared to customer or partner users. See [Implicit Sharing](https://developer.salesforce.com/docs/platform/draes/guide/draes-object-relationships-implicit-sharing.md). - Salesforce must perform these tasks for every customer or partner account that the user owns. - Salesforce also recalculates all sharing rules that include the user’s old or new role in the source group. It removes all of the user’s records from the scope of sharing rules where the old role is the source group and adds those records to the scope of rules where the new role is the source. **note:** If the user owns customer or partner accounts, and there are sharing rules that use customer or partner account roles as the source group, Salesforce sometimes must recalculate those rules. Some sharing rules might no longer be valid given the user’s new location in the hierarchy, in which case an administrator might need to modify or delete them. During the user’s move, the managers in the branch above the user’s old role lose access to all the data that the user owns, as well as to child records shared through the managers’ role settings. Managers in the branch above the user’s new role gain access to the user’s accounts and to child records according to their own role settings. From this example, you can see that a lot can happen under the hood when an administrator takes what looks like a simple action, such as changing the role of a user. We chose this operation to illustrate all the possible types of sharing maintenance, but other common group and data updates can have a similar impact. - Moving a role to another branch in the hierarchy - : One benefit to moving a whole role is that any customer or partner accounts simply move along with their parent role, and Salesforce doesn't have to change the related sharing. On the other hand, Salesforce must do all of the work involved in moving a single user for all users in the role being moved and for all of those users' data. - Changing the owner of a customer or partner account - : The effort required for what looks like a simple data update—changing the name of the user in the Account Owner field—can be surprising. When the old and new owners are in different roles, Salesforce isn’t only moving the customer and partner account roles to a new parent role but also adjusting the sharing for all the data associated with the customer or partner account. # Headless Forgot Password Flow: Change the User’s Password After you receive a one-time password (OTP) from Salesforce, you can change your password with a new request to the forgot password endpoint. This example walks you through changing the password in Postman. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Forgot Password - Change Password**. Note the location of the POST request. It’s the `/services/auth/headless/forgot_password` endpoint on your Experience Cloud site, which is the same endpoint you used for the initial reset request. 2. To see the request body, click **Body**. The password reset request includes these parameters. - `username`—The user’s registered username - `newpassword`—The user’s new password. - `otp`—The one-time password (OTP) sent to the user’s email 3. Replace the body parameter values with your own information. 1. For `username`, enter the username that you registered and logged in with. 2. For `newpassword`, enter a new password that includes uppercase and lowercase characters, a number, and a special character. 3. For `otp`, enter the OTP that you received from your initial request to the forgot password endpoint. 4. To send the request to Headless Forgot Password API, click **Send**. Salesforce validates the OTP and finishes resetting the password. If the request is successful, Salesforce sends a success response. Here’s an example response in Postman. ```nolang { "status_code": "success" } ``` # Headless Forgot Password Flow: Send a Password Reset Request To initialize the Headless Forgot Password Flow, send a password reset request. This example walks you through sending a request with Postman. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Forgot Password - Initialize**. Note the location of the POST request. It’s the `/services/auth/headless/forgot_password` endpoint on your Experience Cloud site. 2. To see the request body, click **Body**. The password reset request includes these parameters. - `username`—The user’s registered username - `recaptcha`—A reCAPTCHA token. Because you configured your flow to require reCAPTCHA , you must include this parameter. 3. Replace the body parameter values with your own information. 1. For `username`, enter the username that you registered and logged in with. 2. For `recaptcha`, enter a valid reCAPTCHA token from Google. You get this token when you implement reCAPTCHA on your app. 4. To send the request to Headless Forgot Password API, click **Send**. If the request is successful, Salesforce sends a one-time password (OTP) to your email address. Here’s an example response in Postman. To avoid leaking user information, Salesforce always returns this response if reCAPTCHA validation succeeds, even if the user doesn’t exist. ```nolang { "status_code": "otp_sent" } ``` 5. Check your email to confirm that you received the OTP. Next, use the OTP to finish changing your password. # Headless Forgot Password JavaScript Examples Use these high-level examples to understand how to implement the Headless Forgot Password Flow for a single-page app. The Headless Forgot Password Flow contains two requests to the same endpoint. You initialize the flow with a request to Headless Forgot Password API. Here’s a function that you can call to initialize the password reset. ```nolang //This is the function call to initialize the forgot password request forgotPasswordRequest(username, null, null, recapchaToken, forgotPasswordProcess.init, callbackFunction); ``` This request results in Salesforce sending the user a one-time password (OTP). For the second request, you pass the username, new password, and OTP to Headless Forgot Password API. Here’s a function that you can call to complete the password change. ```nolang //This is the function call to complete the forgot password request forgotPasswordRequest(username, password, otp, null, forgotPasswordProcess.changePassword, callbackFunction) ``` Because both requests call the same endpoint, you can use one function for both calls. ```nolang function forgotPasswordRequest(username, password, otp, recapchaToken, forgotPasswordProcessStep, callbackFunction) { client = new XMLHttpRequest(); client.open("POST", expDomain + forgotPasswordURI, true); client.setRequestHeader("Content-Type", "application/json"); requestBody = { username: username, newpassword: password, otp: otp, recaptcha: recapchaToken } client.send(JSON.stringify(requestBody)); client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { callbackFunction(JSON.parse(client.response), username, forgotPasswordProcessStep) } else { this.onError("An Error Occured during Forgot Password Step: " + forgotPasswordProcessStep, client.response); } } } } ``` # Headless Forgot Password Overview If your users can log in and register, they must also be able to reset their passwords. Here’s a high-level overview of how the Headless Forgot Password Flow works with a single-page app. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| ![The flow includes an initial request, a response from Salesforce, an email, a second request, and a final success message from Salesforce.](https://developer.salesforce.com/media/headless-identity-impl-guide/headless_forgot_password_flow.png) - An end user clicks a password reset link in your app. - In your app, you natively display a forgot password page. - The user enters their username. - Your app initiates the flow with a request to Headless Forgot Password API. The request includes the user’s username (1). - Salesforce receives the forgot password details. - Salesforce returns a success message to your app (2). - Immediately after, Salesforce generates a one-time password (OTP) and sends it to the user’s email address (3). - In your app, you natively display an OTP verification form. - The user receives the OTP email. - The user enters the OTP and their new password in your verification form. - Your app finishes resetting the password with another request to Headless Forgot Password API. The request includes the username, OTP, and new password (4). - Salesforce verifies the OTP and sets a new password. - Salesforce returns a success message to your app (5). - The user logs in with their new password. Here are a few key concepts to keep in mind for this flow. - This flow sends two requests to Headless Forgot Password API. - The first request initializes the password reset and kicks off the OTP verification process. - The second request completes the password reset process. # Headless Identity Implementation Guide Previous PDF Versions | API Version | Release | PDF | | ----------- | ---------- | --------------------------------------------------------------------------------------------------------- | | 68.0 | Winter '27 | [Download PDF](https://resources.docs.salesforce.com/264/latest/en-us/sfdc/pdf/headless_identity_impl_guide.pdf) | | 67.0 | Summer '26 | [Download PDF](https://resources.docs.salesforce.com/262/latest/en-us/sfdc/pdf/headless_identity_impl_guide.pdf) | | 66.0 | Spring '26 | [Download PDF](https://resources.docs.salesforce.com/260/latest/en-us/sfdc/pdf/headless_identity_impl_guide.pdf) | | 65.0 | Winter '26 | [Download PDF](https://resources.docs.salesforce.com/258/latest/en-us/sfdc/pdf/headless_identity_impl_guide.pdf) | # Headless Login JavaScript Examples Use these high-level examples to understand how to implement headless login for a single-page app. ## Authorization Request The Authorization Code and Credentials Flow enables users to log in with a username and password. A core concept of this flow is making an authorization request to Headless Login API. This client-side JavaScript example shows you how to send an authorization request. ```nolang // Make a POST Request to Authorize client = new XMLHttpRequest(); client.open("POST", expDomain + authorizationURI, true); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //Headers for the Username and Password Code and Cred Flow client.setRequestHeader("Auth-Request-Type", "Named-User"); client.setRequestHeader("Authorization", "Basic " + btoa(username + ':' + password)); //Request Body requestBody = "response_type=code_credentials&client_id=" + clientId + "&redirect_uri=" + callbackURL; // Add State if (state != null) { requestBody = requestBody + '&state=' + storeState(state); } // Add Scopes if (scopes != null) { requestBody = requestBody + '&scope=' + scopes; } // PKCE Enabled requestBody = requestBody + "&code_challenge=" + generateCodeChallenge(); // Send the Authorization Request client.send(requestBody); // Handle the Authorization Response client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { //Auth Code has been returned, perform token exchange tokenExchange(JSON.parse(client.response), null, authorizeType, uniqueVisitorId); } else { onError("An Error Occured during Authorize", client.response); } } } ``` ## Token Exchange After receiving the authorization request, Salesforce validates the username and password and returns an authorization code. The app then calls the token endpoint to exchange the code for an access token. This client-side JavaScript example shows the token exchange. ```nolang function tokenExchange(response, codeChallenge, authorizeType, uniqueVisitorId) { // Get Values from Code Response code = response.code; stateIdentifier = response.state; baseURL = response.sfdc_community_url; state = null; // validate state if it was present if (stateIdentifier != null) { state = getState(stateIdentifier, true); if (state == null) { onError("A state param was sent back but no state was found"); return; } } // Create Client client = new XMLHttpRequest(); client.open("POST", expDomain + tokenURI, true); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // Build Request Body requestBody = "code=" + code + "&grant_type=authorization_code&client_id=" + _this.clientId + "&redirect_uri=" + _this.callbackURL; // Add PKCE requestBody = requestBody + "&code_verifier=" + generateCodeVerifier(); // Send Request client.send(requestBody); client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { //Access Tokens have been returned console.log("Code and Credntial Flow, token response: "); console.log(JSON.parse(client.response)); } else { onError("An error occured during token exchange for " + authorizeType, client.response) } } } } ``` The result is an access token that you can use to request user information and establish the user’s session. # Headless Login Overview You configure headless login via the Authorization Code and Credentials Flow, which is built on the OAuth 2.0 Authorization Code grant type. At the end of this flow, a user is logged in and can access Salesforce data. Here’s a high-level overview of how the flow works with a single-page app. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| ![Diagram showing Authorization Code and Credentials Flow for single-page apps](https://developer.salesforce.com/media/headless-identity-impl-guide/authcodecreds_singlepageapp.png) - Your user goes to your custom app, where your login form is natively displayed, and enters their username and password (1). - If you’re using the Proof Key for Code Exchange (PKCE) extension, the app generates values to verify the authorization code. If you’re not using PKCE, your flow skips this step. We strongly recommend that you always use PKCE when implementing this flow for single-page apps (2). - From the browser, your custom app—via JavaScript—sends a headless authorization request to the Salesforce Headless Login API authorization endpoint on your Experience Cloud site (3). - Salesforce returns a 302 redirect to a preconfigured URL containing the authorization code. If the flow is executed in the browser, the 302 redirect is processed and the response is delivered headlessly to your callback endpoint. For single-page apps, you can use the OAuth 2.0 echo endpoint, which is designed to make development for this use case easier (4). - The callback endpoint extracts the authorization code from the 302 redirect and returns it to the app (5). - The client-side JavaScript receives the redirect URL parameters and initiates the code exchange with a POST request to the token endpoint (6). - Salesforce Headless Login API validates the request and returns an access token response to the app (7). - Client-side JavaScript on the app processes the access token and creates the user’s session (8). - The user is now logged in, and they perform an action in your custom app that initiates a request for Salesforce data. For example, they click a button to access their travel booking history, which is stored in the Salesforce Experience Cloud site (9). - Your custom app makes an authenticated request to a protected Salesforce endpoint, such as a Salesforce API (10). - The user can now access their protected data in your custom app. For example, they can see their travel booking history (11). Here are a few key concepts to keep in mind for this flow. - Your app initiates headless login with an authorization request to Headless Login API. - You use a callback endpoint to extract the authorization code. To make this process easier, instead of building your own endpoint, use the Salesforce OAuth 2.0 echo endpoint. - Your app exchanges the code for an access token with a request to the token endpoint. # Headless Login: Send a Token Request After you send the authorization request and get a code, exchange the code for an access token. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Username Password Login - Token Exchange**. Note the location of the POST request. It’s the `/services/oauth2/token` endpoint on your Experience Cloud site. 2. To see the request body, click **Body**. The token request for headless login includes these parameters. - `code`—The authorization code from Salesforce. - `grant_type`—The OAuth 2.0 grant type. Because the OAuth 2.0 authorization code grant type is the foundation of this flow, this parameter is set to `authorization_code`. - `client_id`—The external client app consumer key. - `redirect_uri`—The external client app callback URL, which points to the OAuth 2.0 echo endpoint. 3. For the `code`, enter the authorization code that you received in your response from the previous step. 4. To send the request to the token endpoint, click **Send**. Salesforce validates the token request and returns a response to your app. The response contains an access token that can be used to access Salesforce APIs and other identifying parameters. Here’s an example access token response in Postman. ```nolang { "access_token": "00DR**********", "sfdc_community_url": "https://MyExperienceCloudSite.my.site.com", "sfdc_community_id": "0DBXXXXXXXXXXXXXXXX", "signature": "CPk2JprUcxOmRHND71gJFn+SxyKe7jWqA1rQnFz9zZg=", "token_format": "opaque", "scope": "openid api", "id_token": "eyJr**************", "instance_url": "https://yourInstance.salesforce.com", "id": "https://yourInstance.salesforce.com/id/00Dxxxxxxxxxxxx/005xxxxxxxxxxxx", "token_type": "Bearer", "issued_at": "1667600739962" } ``` # Headless Login: Send an Authorization Request To configure headless login, set up the Authorization Code and Credentials Flow. This Postman example walks you through the Authorization Code and Credentials Flow with a single-page app. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Username Password Login - Authorize**. Note the location of the POST request. It’s the `/services/oauth2/authorize` endpoint on your Experience Cloud site. 2. To see the headers, click **Headers**. The authorization POST request includes these headers and values. - An `Auth-Request-Type` header set to `Named-User` - An `Authorization` header with the value `Basic `, which contains the Base64-encoded username and password value 3. Update the Authorization header. 1. In a text editor, paste the username that you registered during headless registration. 2. After the username, enter a colon, and then paste the password that you registered, such as `username:password`. 3. Base64-encode the resulting value. For example, if you Base64-encode the string `username:password`, you get `dXNlcm5hbWU6cGFzc3dvcmQ=`. 4. In Postman, paste this value into the Authorization header to replace ``. 4. To see the request body, click **Body**. The authorization request for registration includes these parameters. - `response_type`—The type of response you want to receive. For this example, it’s set to `code_credentials`. - `client_id`—The external client app consumer key - `redirect_uri`—The external client app callback URL, which points to the OAuth 2.0 echo endpoint on your Experience Cloud site 5. To send the request to Headless Login API, click **Send**. Salesforce validates the user credentials and returns a 302 redirect to a preconfigured URL containing the authorization code. Salesforce then automatically sends the redirect response to the redirect URL. The echo endpoint extracts the code and other parameters from the 302 redirect and returns them to your app in JSON format. Here’s an example response in Postman. ```nolang { "code": "aPrxCdr***************", "sfdc_community_url": "https://MyExperienceCloudSite.my.site.com", "sfdc_community_id": "0DBXXXXXXXXXXXXXXXX" } ``` # Headless Registration and Headless Login Flows: Get User Info Both headless registration and headless login are built on the Authorization Code and Credentials Flow, where you exchange an authorization code for an access token. After your app receives the access the token, the next step is processing it and creating the user session. This example walks you through calling the User Info endpoint to confirm that the login was successful and to provide the user information required for your app to create a session. You can use this example for both headless registration and headless login. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Get User Info**. Note the location of the POST request. It’s the `/services/oauth2/userinfo` endpoint on your Experience Cloud site. 2. To see the headers, click **Headers**. The request for User Info includes these headers and values. - An `Authorization` header with the value `Bearer ` - A `Content-Type` header set to `application/json` 3. For the `Authorization` header, replace `` with the access token that you received during headless registration or headless login, depending on which process you’re testing. 4. To send the request to the User Info endpoint, click **Send**. If the login was successful, you get a response containing information about the user. # Headless Registration Flow: Send a Registration Request To configure the Headless Registration Flow, start with a request to Headless Registration API. This example walks you through sending a request with Postman. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| For the Headless Registration Flow, we use these three examples from the headless Postman collection. - Registration - Initialize - Registration - Authorize - Registration - Token Exchange 1. From the Headless Identity API Demo folder in Postman, select **Registration - Initialize**. Note the location of the POST request. It’s the `/services/auth/headless/init/registration` endpoint on your Experience Cloud site. 2. To see the request body, click **Body**. The body for the initial registration request includes these parameters. - `userdata`—Contains basic information about the user, including their first name, last name, email address, and username. - `customdata`—Contains custom information that you want to collect in addition the data in the `userdata` parameter. In this example, it contains the user’s mobile phone number, street address, city, state, ZIP code, and privacy policy. - `password`—The user’s password. - `recaptcha`—A reCAPTCHA token. Because you configured your flow to require reCAPTCHA, this parameter is required. - `verificationmethod`—The method you want to use to verify the user’s identity. They can use email or SMS. For this example, we use email. 3. For the request body, update the parameter values that aren’t filled out already. 1. In the `userdata` parameter, change `userFirstName` and `userLastName` to your own first name and last name. 2. For `email`, enter your own email address. 3. For `username`, enter a test username. You can use your email address for the username. 4. For `recaptcha`, enter a valid reCAPTCHA token from Google. Generating this token is out of the scope of this guide. Google provides several examples, which you can find at [https://developers.google.com/recaptcha/docs/v3](https://developers.google.com/recaptcha/docs/v3). 4. To send the request to Headless Registration API, click **Send**. Salesforce receives the request and queues the user data to be processed later. It creates an identifier to track the request. If the request is successful, Salesforce sends you a response that includes your verification method and request identifier. Here’s an example response from Postman. ```nolang { "status": "success", "email": "testemail@example.com", "identifier": "dg3o**********" } ``` Because you configured email as the verification method, Salesforce also sends an email containing a one-time password (OTP) to your email address. 5. Check your email for the OTP. You use it in the next step. Next, you send an authorization request to initialize the Authorization Code and Credentials Flow and log the user in. # Headless Registration Flow: Send a Token Request After you receive the authorization code from Salesforce, exchange the code for an access token. This example walks you through initializing the code exchange with Postman. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Registration - Token Exchange**. Note the location of the POST request. It’s the `/services/oauth2/token` endpoint on your Experience Cloud site. 2. To see the request body, click **Body**. The token request for registration includes these parameters. - `code`—The authorization code from Salesforce - `grant_type`—The OAuth 2.0 grant type. Because the OAuth 2.0 authorization code grant type is the foundation of this flow, this parameter is set to `authorization_code`. - `client_id`—The external client app consumer key - `redirect_uri`—The external client app callback URL, which points to the OAuth 2.0 echo endpoint on your Experience Cloud site 3. For the `code`, enter the authorization code that you received after sending the authorization request. 4. To send the request to the token endpoint, click **Send**. Salesforce validates the token request and returns an access token to your app. # Headless Registration Flow: Send an Authorization Request After you send a registration request to Headless Registration API, initialize the Authorization Code and Credentials Flow to complete the registration and log the user in. For this part of the flow, you call Headless Login API. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Registration - Authorize**. Note the location of the POST request. It’s the `/services/oauth2/authorize` endpoint on your Experience Cloud site. 2. To see the headers, click **Headers**. The authorization request for registration includes these headers and values. - An `Auth-Request-Type` header set to `user-registration` - An `Auth-Verification-Type` header set to `email`. This header specifies the method that was used to verify the user’s identity—note that its value matches the `verificationmethod` body parameter from your initial registration request. - An `Authorization` header with the value `Basic ` 3. Update the Authorization header. 1. In a text editor, paste the request ID from the registration response from the previous step. 2. After the request ID, enter a colon, and then paste the one-time password (OTP) that you received from Salesforce, such as `identifier:OTP`. 3. Base64-encode the resulting value. For example, if you Base64-encode the string `identifier:OTP`, you get `aWRlbnRpZmllcjpPVFA=`. 4. In Postman, paste this value into the Authorization header to replace ``. 4. To see the request body, click **Body**. The authorization request for registration includes these parameters, some of which you already entered when you set your variables. - `response_type`—The type of response you want to receive. For this flow, it’s set to `code_credentials`. - `client_id`—The external client app consumer key - `redirect_uri`—The external client app callback URL, which points to the OAuth 2.0 echo endpoint on your Experience Cloud site - `scope`—An optional comma-separated list of scopes. For this demo, you can leave it blank. 5. To send the request to Headless Login API, click **Send**. Salesforce verifies the request ID and OTP and uses the request ID to retrieve the queued user data from the registration request. Salesforce then calls the headless Apex registration handler that you configured in your Experience Cloud site. The registration handler uses the queued data to create a user. If the request is successful, Salesforce returns a 302 redirect to a preconfigured URL containing the authorization code. The echo endpoint extracts the code and other parameters from the 302 redirect and returns them to your app in JSON format. Here’s an example response in Postman. ```nolang { "code": "aPrxCdr***************", "sfdc_community_url": "https://MyExperienceCloudSite.my.site.com", "sfdc_community_id": "0DBXXXXXXXXXXXXXXXX" } ``` Next, you exchange the authorization code for an access token. # Headless Registration JavaScript Examples Use these high-level examples to understand how to implement headless registration for a single-page app. ## Authorization Request The authorization request for headless registration is similar to the request for headless login, but instead of passing the username and password in the header, you pass in the request identifier and OTP. The request also contains a few additional headers that aren’t required for headless login. Here’s an example. ```nolang // Make a POST Request to Authorize client = new XMLHttpRequest(); client.open("POST", expDomain + authorizationURI, true); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //Headers for registration variation of the Code and Credentials flow client.setRequestHeader("Auth-Request-Type", "user-registration"); client.setRequestHeader("Auth-Verification-Type", verificationMethod); //Request identifier is returned from the /init/registration endpoint, and requestCredential is the OTP sent in the email or SMS. client.setRequestHeader("Authorization", "Basic " + btoa(requestIdentifier + ':' + requestCredential)); //Request Body requestBody = "response_type=code_credentials&client_id=" + clientId + "&redirect_uri=" + callbackURL; // Add State if (state != null) { requestBody = requestBody + '&state=' + storeState(state); } // Add Scopes if (scopes != null) { requestBody = requestBody + '&scope=' + scopes; } // PKCE Enabled requestBody = requestBody + "&code_challenge=" + generateCodeChallenge(); // Send the Authorization Request client.send(requestBody); // Handle the Authorization Response client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { //Auth Code has been returned, perform token exchange tokenExchange(JSON.parse(client.response), null, authorizeType, uniqueVisitorId); } else { onError("An Error Occured during Authorize", client.response); } } } ``` Salesforce validates the request identifier and OTP and registers the user by calling the headless registration handler. When the user is created, Salesforce returns an authorization code response. You use a callback endpoint to extract the code and other parameters and return them to your app. ## Token Exchange When your app receives the authorization code, it exchanges the code for an access token. This part of the flow is identical to the token exchange in headless login. ```nolang function tokenExchange(response, codeChallenge, authorizeType, uniqueVisitorId) { // Get Values from Code Response code = response.code; stateIdentifier = response.state; baseURL = response.sfdc_community_url; state = null; // validate state if it was present if (stateIdentifier != null) { state = getState(stateIdentifier, true); if (state == null) { onError("A state param was sent back but no state was found"); return; } } // Create Client client = new XMLHttpRequest(); client.open("POST", expDomain + tokenURI, true); client.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // Build Request Body requestBody = "code=" + code + "&grant_type=authorization_code&client_id=" + _this.clientId + "&redirect_uri=" + _this.callbackURL; // Add PKCE requestBody = requestBody + "&code_verifier=" + generateCodeVerifier(); // Send Request client.send(requestBody); client.onreadystatechange = function() { if(this.readyState == 4) { if (this.status == 200) { //Access Tokens have been returned console.log("Code and Credntial Flow, token response: "); console.log(JSON.parse(client.response)); } else { onError("An error occured during token exchange for " + authorizeType, client.response) } } } } ``` The result is an access token that you can use to request user information and establish the user’s session. # Headless Registration Overview The Headless Registration Flow extends the Authorization Code and Credentials Flow. At the end of this flow, a new user is registered and logged in, and they can access Salesforce data. Here’s a high-level overview of how the flow works with a single-page app. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| - An end user opens your app and clicks **Register** (1). - In your app, you natively display a registration form to collect user data. The look and feel of this form is entirely up to you, and you can fully customize what kind of information that you want to collect (2). - The end user enters their information in the app. For example, they enter their new username, password, and first name (3). - Your app submits the user information to the Headless Registration API endpoint on your Experience Cloud site (4). - Salesforce receives the user information and queues it to be processed later (5a). - Salesforce then sends an email or an SMS text message containing a one-time password (OTP) to the user (5b). - In your app, you natively display an OTP verification form. Again, it’s up to you how you want this form to look (6). - The user receives their OTP and enters it in the verification form (7). - Your app then initializes the Authorization Code and Credentials Flow with an authorization code request. The request includes the OTP and the request ID, along with other parameters (8). - Salesforce verifies the request ID and OTP. It retrieves the queued user data that it stored earlier and calls the headless registration handler. The headless registration handler creates a user in Salesforce (9). - Salesforce returns a 302 redirect to a preconfigured URL containing the authorization code. If the flow is executed in the browser, the 302 redirect is processed and the response is delivered headlessly to your callback endpoint. For single-page apps, you can use the OAuth 2.0 echo endpoint, which is designed to make development for this use case easier (10). - The callback endpoint extracts the code and other parameters from the 302 redirect. It returns this information to your app (11). - Your app initiates the code exchange via a POST request to the token endpoint (12). - From the token endpoint, Salesforce returns an access token response to your app (13). - Your app processes the token response and creates the user’s session (14). - The user is now registered and logged in. They perform an action in your custom app that initiates a request for Salesforce data. For example, they click a button to access their travel booking history, which is stored in the Salesforce Experience Cloud site (15). - Your custom app makes an authenticated request to a protected Salesforce endpoint, such as a Salesforce API (16). - The customer can now access their protected data in your custom app. For example, they can see their travel booking history (17). ![Sequence diagram showing headless registration with a single-page app](https://developer.salesforce.com/media/headless-identity-impl-guide/headless_registration_spa_diagram.png) Here are a few key concepts to keep in mind. - Your app initiates registration with a request to Headless Registration API. - When Salesforce receives the user information from this request, it queues the information to be processed later. At this point, the user isn’t registered yet. - Your app initializes the Authorization Code and Credentials Flow with an authorization request to Headless Login API. - The information sent in this authorization request prompts Salesforce to retrieve the queued user information and pass it to the headless registration handler. The registration handler creates the user. - At the end of the flow, the user is registered and logged in. # Headlessly Revoke an Access Token To log out of your headless app, you can revoke the access token. This example walks you through headlessly revoking a token with Postman. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| 1. From the Headless Identity API Demo folder in Postman, select **Revoke Token**. Note the location of the POST request. It’s the `/services/oauth2/revoke` endpoint on your Experience Cloud site, which is the same endpoint that you used for the initial reset request. 2. To see the request body, click **Body**. The revocation request includes only a `token` parameter. 3. For the `token` parameter, replace `` with an access token that you obtained during headless registration or headless login. 4. To send the request to the revocation endpoint, click **Send**. If the request is successful, you’re logged out. # HistoryRetentionJob Represents the body of retained data from the archive, and the status of the archived data. Available in API version 29.0 or later. ## Supported SOAP API Calls describeSObjects(), query() ## Supported REST API HTTP Methods `GET` ## Fields ### DurationSeconds - **Type**: int - **Properties**: Filter, Group, Nillable, Sort - **Description**: How many seconds the field history retention job took to complete (whether successful or not). ### HistoryType - **Type**: picklist - **Properties**: Create, Filter, Group, Nillable, Restricted picklist, Sort - **Description**: The object type that contains the field history that you retained. For custom objects, use the object name. - **Possible values**: - `Account` - `Case` - `Contact` - `Leads` - `Opportunity` ### NumberOfRowsRetained - **Type**: int - **Properties**: Filter, Group, Nillable, Sort - **Description**: The number of field history rows that a field history retention job has retained. ### RetainOlderThanDate - **Type**: dateTime - **Properties**: Filter, Sort - **Description**: The date and time before which all field history data was retained. ### StartDate - **Type**: dateTime - **Properties**: Filter, Nillable, Sort - **Description**: The start date of the field history retention job. ### Status - **Type**: picklist - **Properties**: Filter, Group, Nillable, Restricted picklist, Sort - **Description**: Provides the status of the field history retention job. By default, field history tracking copies data to the archive, leaving a duplicate of the archived data in Salesforce. You can delete data from Salesforce manually after it's archived. - **Possible values**: - `CopyScheduled` - `CopyRunning` - `CopySucceeded` - `CopyFailed` - `CopyKilled` - `NothingToArchive` - `DeleteScheduled` - `DeleteRunning` - `DeleteSucceeded` - `DeleteFailed` - `DeleteKilled` # HistoryRetentionPolicy Represents the policy for archiving field history data. When you set a policy, you specify the number of months that you want to keep field history in Salesforce before archiving it. By default, when Field Audit Trail is enabled, all field history is retained. This component is only available to users with the RetainFieldHistory permission. ## Declarative Metadata File Suffix and Directory Location Field history retention policies are defined as part of a standard or custom object. You can set field history retention policies for objects individually. ## Version Available in API version 31.0 and later. ## Fields ### archiveAfterMonths - **Field Type**: int - **Description**: Required. The number of months that you want to keep field history data in Salesforce before archiving. You can set a minimum of 1 month and a maximum of 18 months. If you don't set a number, the default is 18 months. (That is, Salesforce maintains data for 18 months before archiving.) ### archiveRetentionYears - **Field Type**: int - **Description**: The number of years until you manually delete data from the archive. Use this field as a reminder for manually deleting data. By default, field history data isn’t automatically deleted when Field Audit Trail is enabled. ### description - **Field Type**: string - **Description**: A text description for the history retention. ### gracePeriodDays - **Field Type**: int - **Description**: The number of days of extra time after the `archiveAfterMonths` period before the data is archived. The `gracePeriodDays` interval applies only to the first time that the data is archived; because all the data is copied the first time, the operation can take longer than subsequent times when only the data that changed since the last archival operation is copied. The `gracePeriodDays` provides extra time for the administrator to prepare the organization before the initial archive operation. You can set a minimum of zero days and a maximum of 10 days. If no number is set, the default is 1 day. ## Declarative Metadata Sample Definition This sample shows the definition of a history retention policy for a custom object. ```xml 6 5 My field history retention ... ``` # Implement reCAPTCHA on a Web App For the Headless Registration Flow and the Headless Forgot Password Flow, you must configure at least one of two security settings on the Experience Cloud Login & Registration page. These settings add requirements to the requests that your app sends to Headless Identity APIs. You can require your app to send an access token issued to an internal integration user. Or you can require your app to send a reCAPTCHA token. For single-page apps, because you can’t keep the integration user’s access token private in the browser, we recommend that you require your app to send a reCAPTCHA token instead. Requiring reCAPTCHA helps you filter out invalid requests, such as requests from bots. To work with the Headless Identity API Postman collection, you must have a valid reCAPTCHA token. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| Though full-blown instructions for implementing reCAPTCHA are outside the scope of this guide, we show you where to go and what information you need. Salesforce supports reCAPTCHA v2 and reCAPTCHA v3 for Headless Identity. For this example, we use reCAPTCHA v3. To get a reCAPTCHA token, you must set up and host reCAPTCHA on a web app. In production, you implement reCAPTCHA on your off-platform app. For this example, if you don’t have a test web app in mind, you can create one using[ Heroku](https://www.salesforce.com/editions-pricing/heroku/)—you can try a basic account for free. To implement reCAPTCHA, see *reCAPTCHA v3* in the reCAPTCHA Developer’s Guide at [https://developers.google.com/recaptcha/docs/v3](https://developers.google.com/recaptcha/docs/v3). When you set up reCAPTCHA for this example, follow these guidelines. - For the domain, enter the URL for your web app, such as `https://www.myapp.com`. - For reCAPTCHA type, choose reCAPTCHA v3. - Note your API key pair, which you use later in this guide. - Ensure that you can get a reCAPTCHA token before you move on. # Implicit Sharing The sharing capabilities of the Salesforce Platform include a wide variety of features that you can use to explicitly grant access to data for individuals and groups. In addition to these more familiar features, there are a number of sharing behaviors that are built into Salesforce. This kind of sharing is called implicit because it’s defined and maintained by the system to support collaboration among members of sales teams, customer service representatives, and clients or customers. This table describes the different kinds of implicit sharing built into Salesforce applications and the record access that each kind provides. |Type of Sharing|Provides|Details|Example| |---------------|--------|-------|-------| |Parent|Read-only access to the parent account for a user with access to a child case, contact, or opportunity|
  • Not used when sharing on the child is controlled by its parent
  • Expensive to maintain with many account children
  • When a user loses access to a child, Salesforce must check all other children to see if it can delete the implicit parent.
  • Not granted by the View All permission on the child object
|Henry is a Standard User with access to an opportunity through an owner-based sharing rule. He can view data about the opportunity’s parent account, but he can’t edit the account’s data.| |Child|Access to child case, contact, or opportunity records for the owner of the parent account|
  • Not used when sharing on the child is controlled by its parent
  • Controlled by child access settings for the account owner’s role
  • Supports account sharing rules that grant child record access
  • Supports account team access based on team settings
  • When a user loses access to the parent, Salesforce removes the user’s access to all children records
  • Not granted by the View All permission on the parent object
|Henry is a Standard User who owns an account. The Salesforce admin set up Henry’s role so that assigned users can view all opportunities, cases, and contacts associated with accounts they own. Henry can therefore view data for all of the child opportunities, cases, and contacts for the account he owns, but he can’t edit this data.| |Experience Cloud Site|Access to a customer or partner account and all associated contacts for all customer or partner users under that account. Access to a case for the customer or partner user that’s the contact on the case.|Account and associated contacts shared with the lowest role under the customer or partner account|Sarah is a Customer Community Plus User added under the “Acme” account. Sarah has Read access to the “Acme” account as well as Read access to all the other contacts related to the account.| |High Volume:sup[1]|Full access to data owned by high-volume users associated with a sharing set for members of the sharing set's share group|All members of the sharing set’s share group gain full access to every record owned by every high-volume user associated with that sharing set|For your site, a sharing set is created to grant high-volume users on the same account Read Only access to each other’s cases. Internal users added to the sharing set’s share group have full access to these cases.| |High Volume Parent|Read only access to the parent account of records shared through a sharing set's share group for members of the share group|Maintains the ability to see the parent account when users are given access to account children owned by high-volume users|For your site, the same sharing set for cases mentioned in the previous example automatically gives the internal users added to the share group read access to the cases’ parent accounts.| :sup[1]To allow external users to scale into the millions, high-volume users have a streamlined sharing model that doesn’t rely on roles or groups, and functions similarly to calendar events and activities. High-volume users include the Customer Community, High Volume Customer Portal, and Authenticated Website license types. # Introduction This guide introduces advanced topics in user record access, and provides recommendations on how to configure your organization to optimize access control performance. In particular, this guide focuses on the effects of group maintenance on sharing performance and the built-in sharing behaviors that support Salesforce applications. It also: - Addresses how to avoid common configuration traps that can drag down the performance of your record-level access system - Introduces some key platform features that can help you speed up large-scale sharing realignments This guide is intended for expert architects working on Salesforce implementations with complex record access requirements or large-scale sales organization realignments. However, the sharing best practices covered can be applied to orgs of any size. This guide assumes expertise in Salesforce administration and security, and knowledge of SQL and relational database concepts. It also assumes a familiarity with the content in [Record-Level Access: Under the Hood](https://developer.salesforce.com/docs/atlas.en-us.salesforce_record_access_under_the_hood.meta/salesforce_record_access_under_the_hood/uth_intro.htm), which explains the inner workings of the flexible and powerful Salesforce record access infrastructure. # JavaScript Examples for Headless Identity APIs Postman is great for testing and learning how to set up headless identity flows, but it doesn’t show you how your app can interact with Headless Identity APIs. Use these high-level JavaScript examples to understand how your app can call these APIs in a real-world implementation. Like the rest of this guide, the examples here apply to single-page apps, also known as public clients. These examples don’t show you how to use these flows with client-server apps or private clients. |Required Editions| |-----------------| |Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience| |Available in: **Enterprise**, **Unlimited**, and **Developer** Editions| These examples are for demonstration only and aren’t meant to be used in production. Always test code before deploying it to a production environment. Unlike the Postman examples, these examples use the Proof Key for Code Exchange (PKCE) extension, which improves security. We strongly recommend that you always use PKCE when configuring these flows with public clients. The examples present a simplified overview of each headless flow for public clients. For in-depth descriptions of the flows for both public and private clients, see these resources in Salesforce Help. - [Headless Identity APIs for Customers and Partners](https://help.salesforce.com/s/articleView?id=xcloud.headless_identity_customers_overview.htm&type=5) - [Authorization Code and Credentials Flow for Public Clients](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_authcodecreds_singlepageapp.htm&type=5) - [Authorization Code and Credentials Flow for Private Clients](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_authorization_code_credentials_flow.htm&type=5) - [Headless Registration Flow for Public Clients](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_headless_registration_public_clients.htm&type=5) - [Headless Registration Flow for Private Clients](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_headless_registration_private_clients.htm&type=5) - [Headless Forgot Password Flow](https://help.salesforce.com/s/articleView?id=xcloud.remoteaccess_headless_forgot_password_flow.htm&type=5)—You can use this resource for public and private clients. - **[Headless Login JavaScript Examples](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-javascript-login.md)** Use these high-level examples to understand how to implement headless login for a single-page app. - **[Headless Registration JavaScript Examples](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-registration-javascript.md)** Use these high-level examples to understand how to implement headless registration for a single-page app. - **[Headless Forgot Password JavaScript Examples](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-javascript-forgotpassword.md)** Use these high-level examples to understand how to implement the Headless Forgot Password Flow for a single-page app. - **[Building a Native Single Sign-On Experience JavaScript Examples](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-javascript-sso.md)** Use these high-level examples to understand how to create a native single sign-on (SSO) experience for a single-page app. This configuration uses a redirect-based flow to make it seem like your app natively integrates with an SSO provider. It isn’t technically headless, but the user experience is the same as the headless flows. - **[Getting User Info JavaScript Examples](https://developer.salesforce.com/docs/platform/headless-identity-impl-guide/guide/headless-identity-javascript-getuserinfo.md)** When you get an access token via headless registration or headless login, you can retrieve user information with a request to the User Info endpoint. Use this example to understand how. # Lightning Security Develop secure Lightning components and apps by meeting Content Security Policy requirements, enforcing CRUD/FLS and sharing in server-side Apex, and guarding against common attacks like XSS and CSRF. ## General Lightning Security Considerations Third-party Lightning components and apps operate in a special domain (lightning.force.com or lightning.com) that's shared with Salesforce-authored Lightning code -- in particular, setup.app, which controls many sensitive security settings. Visualforce applications, by contrast, are served from a different domain (force.com) that isn't shared with Salesforce code. Because Lightning code shares the same origin as Salesforce-authored code, increased restrictions are placed on third-party Lightning code. These restrictions are enforced by Lightning Locker and a special Content Security Policy. There’s also additional scrutiny in the AppExchange security review. When developing Lightning apps, ensure that the [stricter CSP](https://developer.salesforce.com/docs/platform/lightning-components-security/guide/content-security-policy-stricter.html) setting is enabled. Org admins should enable this setting to protect the org's security controls from vulnerabilities in custom Lightning components. Develop and test your code with stricter CSP enabled in order to ensure compatibility. Note that stricter CSP is enabled by default beginning with Summer '18, but not in orgs created previously. ## Content Security Policy for Lightning Components Lightning components are currently subject to a Content Security Policy (CSP) with the following directives. See [CSP Directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) for more information. ||| |-|-| |**Directive**|**Summary**| |default-src 'self'|Default policy that resources may be only loaded from the same domain (lightning.force.com or lightning.com).| |script-src 'self'|Scripts may only be loaded from the same domain (no external script loads). Use static resources to store all scripts.| |'unsafe-inline'|Inline JavaScript is not blocked by the CSP but is blocked in the security review as future CSP settings will not allow unsafe-inline. Do not write any code using inline JS in order to prevent your components from malfunctioning when CSP is tightened.| |object-src 'self'|\ \ and \ elements can only be loaded from the same domain (use static resources).| |style-src 'self'|CSS styles can only be loaded from the same domain (use static resources).| |img-src 'self'|Images can only be loaded from the same domain.| |img-src 'http:' 'https:' 'data:'|Images can only be loaded via http, https, or data URIs. The security review requires https.| |style-src 'https:'|CSS styles can only be loaded via https.| |media-src 'self'|Audio and video elements can only be loaded from the same domain.| |frame-ancestors https:|The page can be embedded only via an https parent frame.| |frame-src https:|All frames must be loaded via https.| |font-src https: data:|Fonts can be loaded via https and data URIs.| |connect-src 'self'|XHR callbacks and websockets can only connect back to the same domain.| |'unsafe-eval'|eval() and related reflection operations are *not* blocked by the CSP but are blocked in the AppExchange security review.| ## Additional Restrictions For JavaScript in Lightning Components **note:** As of Spring ’19 (API version 45.0), you can build Lightning components using two programming models: the Lightning Web Components model, and the original Aura Components model. Lightning web components are custom HTML elements built using HTML and modern JavaScript. Lightning web components and Aura components can coexist and interoperate on a page. In addition to CSP directives, secure components also meet the following restrictions. |Restriction|Aura Components|Lightning Web Components| |-----------|---------------|------------------------| |Components may only read or modify DOM elements belonging to the same namespace. To pass data they must communicate through the public API.|Set Aura attributes, call Aura methods, or use Aura or DOM events.|Use methods and attributes with the @api decorator or use events.| |Events must not be fired in a renderer function|Fire events from a controller method or a helper method called by a controller method.|No restriction.| |Component attributes must not be changed during a render cycle to avoid render loops.|Modify attributes from a controller method or a helper method called by a controller method.|Components should not modify their own attributes in a `renderedCallback`.| |Components must properly load resources like scripts and stylesheets instead of using `script` or `link` tags.|Use the [ltng:require](https://developer.salesforce.com/docs/component-library/bundle/ltng:require/documentation) component.|Use the [lightning/platformResourceLoader](https://developer.salesforce.com/docs/component-library/bundle/lightning-platform-resource-loader/documentation) module| |Components must not override native window or document functions.|Aura components must not override window or document functions.|Lightning web components must not override window or document functions| |Inline JavaScript must only be used for methods in the component markup. For example `
foo
`|Aura component method: `
foo
`|Lightning web component method`
foo
`| |Components submitted for security review must include all CSS and JavaScript libraries in static resources.|Load all libraries using `ltng:require` from a `$Resource` URL.|Load all libraries using `lightning/platformResourceLoader` from a `$Resource` URL.| You should also be familiar with the restrictions listed in [Security with Lightning Locker](https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.security_locker_service). When you submit a Lightning component or app for security review, include all source JavaScript files in static resources, as we cannot review minified code directly. Failure to do so delays the review of your components until we get the appropriate source files. This also applies to sources that compile to JavaScript. ## Component Security Boundaries and Encapsulation In Apex, every method that is annotated @AuraEnabled should be treated as a webservice interface. That is, the developer should assume that an attacker can call this method with any parameter, even if the developer's client-side code does not invoke the method or invokes it using only sanitized parameters. Therefore the parameters of an @AuraEnabled method should: - not be placed into a SOQL query unsanitized - not be trusted to specify which fields and objects a user can access Whenever an @AuraEnabled method modifies sObjects, full CRUD/FLS as well as sharing checks should be made to ensure that the client does not elevate their privileges when invoking this method. These checks need to be performed on the server (in Apex). Note that this is different than the situation with Visualforce, in which CRUD/FLS checks can be performed for you by the Visualforce presentation layer. This means porting code from Visualforce to Lightning requires the addition of CRUD/FLS checks each time an sObject is accessed. Because Lightning components are meant to be re-usable and shareable, each global or public attribute should be viewed as untrusted from the point of view of the component's internal logic. In other words, don't take the contents of an attribute and render them directly to the DOM via innerHTML or $().html(). It does not matter whether, in your app, the attributes are provided by another component you control. When you need to perform a raw HTML write or set an href attribute, then the attribute must be marked sanitized in your JavaScript code. An important aspect to understand is how session authentication works for your AuraEnabled components. If an Experience Cloud site user's session expires, and the rendered page contains Lightning components that can invoke custom Apex methods (AuraEnabled), the methods are invoked as the site's guest user. Plan your implementation to either provide/revoke access to the site guest user or to monitor for session time-outs to invoke login requests as needed. ## Access Control in Apex Controllers and Supporting Classes When Lightning components invoke server-side controllers, the developer must ensure that the server-side read/write operations don’t subvert the organization's security policy as set by the user's profile and sharing permissions. All access control enforcement must occur server-side, because the client is under the control of the attacker. Fortunately you can ensure that your server-side code is safe to use with Lightning components by taking some additional steps when writing your Apex classes. ## Sharing in Apex Classes All controller classes *must* have the `with sharing` keyword. There are no exceptions. In some cases, your code needs to elevate privileges beyond those of the logged in user. For example, you may have a method that returns summary data computed over fields that the logged-in user cannot access. In this case, the controller must still use the `with sharing` keyword, but a specific Aura-enabled method may call a helper method in a class that is explicitly marked without sharing. All privileged operations should be placed into these helper classes, and each privileged helper class should perform a single privileged function and no unprivileged functions. All other classes must be `with sharing`. ```apex public class ExpenseController() { //Unsafe @AuraEnabled public static String getSummary() { doPrivilegedOp() //should not be here } } ``` ```html public with sharing class ExpenseController() { //safe @AuraEnabled public static String getSummary() { HelperClass.doPrivilegedOp() } } ``` ```apex public without sharing HelperClass() { //safe, not a controller and limited functionality protected static String doPrivilegedOp() { //calculate roll-up field here } } ``` Avoid ambiguous sharing policies that make auditing difficult, particularly in large apps with complex control flows. Write apps in such a way that an auditor who looks at a class that performs a database operation can quickly and accurately determine if the database operation respects user permissions. All global classes, classes that expose webservices, or allow for remote invocation (such as Aura Enabled classes and remote action classes) must always use the `with sharing` keyword. The exception is for controller classes in sites, including Experience Cloud sites, where a `with sharing` policy could force the granting of excessive permissions to guest users. ## CRUD/FLS Enforcement CRUD/FLS permissions aren’t automatically enforced in Lightning components or controllers. You can’t rely on Lightning components to enforce security as the client is under the control of the attacker, so all security checks must always be performed server-side. You must explicitly check for `isAccessible()`, `isUpdateable()`i, `isCreateable()`, and `isDeletable()` prior to performing these operations on sObjects. ```apex public with sharing class ExpenseController { //ns = namespace, otherwise leave out ns__ @AuraEnabled public static List get_UNSAFE_Expenses() { //vulnerable return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c, ns__Reimbursed__c, CreatedDate FROM ns__Expense__c]; } @AuraEnabled public static List getExpenses() { //safe String [] expenseAccessFields = new String [] {'Id', 'Name', 'ns__Amount__c', 'ns__Client__c', 'ns__Date__c', 'ns__Reimbursed__c', 'CreatedDate' }; // Obtaining the field name/token map for the Expense object Map m = Schema.SObjectType.ns__Expense__c.fields.getMap(); for (String fieldToCheck : expenseAccessFields) { // Check if the user has access to view field if (!m.get(fieldToCheck).getDescribe().isAccessible()) { //also pass error to client throw new System.NoAccessException() //included to quiet editor return null; } } //now it is safe to proceed with call return [SELECT Id, Name, ns__Amount__c, ns__Client__c, ns__Date__c, ns__Reimbursed__c, CreatedDate FROM ns__Expense__c]; } } ``` ## Field Validation Concerns For purposes of threat modeling, the client is under the control of the attacker, therefore you cannot rely on client-side field validation to sanitize data written to the server. Client-side field validation has an important usability role -- avoiding round trips for normal (non-malicious) users. Nevertheless, if you require field-level validation of your sObject data model, then this must be performed with triggers. Note that validation in the Aura-enabled server-side controller is also insufficient as users can use the SOAP/REST API to modify objects directly, bypassing the Apex Controller as well as the UI. ## Object Validation Concerns When Aura enabled methods receive objects as input types, you must validate the fields within those objects, since any fields could be set by the client. Here’s a typical vulnerable example. ```apex @AuraEnabled public static Account insertAccount(Account a){ // We expect only Name to be set if(Schema.SObjectType.Account.fields.Name.isUpdatable()){ insert a; // <== problem: if Account "a" includes other fields // or custom fields they will also be set. return a; } return new Account(); } ``` To do this, you must iterate through all populated fields and check FLS on those fields. ```apex @AuraEnabled public static Account respectFLS(Account a){ Map fieldMap = Schema.SObjectType.Account.fields.getMap(); Map fieldsToValue = a.getPopulatedFieldsAsMap(); for(String fieldKey : fieldsToValue.keySet() ) { // if this populated field is not accessible, throw an error if(! fieldMap.get(fieldKey).getDescribe().isUpdateable()) { throw new SecurityException('Invalid fields'); } } insert a; return a; } ``` ## Cross Site Request Forgery In order to prevent [CSRF attacks](https://developer.salesforce.com/docs/platform/secure-coding/guide/secure-coding-cross-site-req-forgery.md), do not invoke any server-side controller method that performs a DML operation automatically as the result of a page load. Specifically, do not invoke server-side DML controller method as onInit handlers, or afterRender handlers (if rendering is performed automatically on page load). ```apex ({ doInit: function(cmp) { var action = cmp.get("c.updateField"); //vulnerable to CSRF [...] $A.enqueueAction(action); }, handleClick: function(cmp, event) { var action = cmp.get("c.updateField"); //not vulnerable to CSRF [...] $A.enqueueAction(action); } }) ``` The key is that the DML operation not be performed without an event stemming from human interaction, such as a click. CSRF only applies to server-side DML operations, not operations that update client-side component attributes. ## Cross Site Scripting Component markup is rendered differently than standard Visualforce markup (which is rendered server-side) or javascript micro-templating frameworks (which are usually rendered with innerHTML). In Lightning, component markup must be valid xhtml, and the markup is parsed and rendered to the DOM with standard DOM accessors such as setAttribute and textContent. Therefore no html parsing occurs during component markup rendering, and there is no possibility of breaking out of attribute values or nodeValues. When attributes are interpolated into markup, they can only occur as attribute values or the text content of DOM nodes. Lightning attribute values cannot be inserted as attribute names, or portions of attribute values. This means certain constructions that would be valid in Visualforce or most micro-templating frameworks are invalid aura expressions, and most of the remaining constructions are automatically safe: ```html
Here is a {!v.myvalue} bold value
a div
a div
a div
a div
a div
``` Because of this, no encoding is ever performed by the framework when rendering component markup, nor are any encoding functions provided by the framework. However, there is still the possibility of using unsafe attributes: ```html click //unsafe: foo=javascript:..