BLOG ARTICLE

Generate Magic Links for your users in Experience Cloud

Share This Post

In the ever-evolving digital landscape, user experience remains paramount. Simplifying authentication processes is crucial for enhancing user engagement and satisfaction. 

For example, sometimes when we are sending an email to a customer we’d want to skip authentication and open the desired page straight away.

In those cases Magic Links come to help!

What are Magic Links?

A magic link, also known as a one-time login (OTL) or passwordless login link, is a unique, time-sensitive URL sent to a user’s registered email address as a secure means of authentication. This link, when clicked, automatically logs the user into their account without requiring them to enter a password.

How to generate Magic Links in Experience Cloud (Community Cloud)?

Most common way of generating them is to have a separate custom object that manages “token”. Magic Link would contain said token and point to a public page with Apex Controller which validates it in the background and operates in “without sharing” mode as System to get further information and display it to users.

While this method does work, it creates a lot of room for mistakes and potential data exposure. It also doesn’t actually create a proper user session with Salesforce, which prevents users from freely browsing Community further.

So is there a better way?

Well, there is a frontdoor.jsp endpoint which can accept access_token from an OAuth authentication with web scope and convert it into a valid Web Session:

https://instance.site.com/secur/frontdoor.jsp sid=ACCESS_TOKEN&retURL=RELATIVE_URL_TO_OPEN

We can’t share users’ actual access_token as we might not have it during link generation and we can’t dynamically control its expiration, but we can use OAuth 2.0 JWT Bearer Flow to generate it without user interaction!

This is how end flow will look like:

JWT Token Generation

To generate a JWT token that will be sent to a customer within Magic Link, we’ll need to set up an OAuth 2.0 JWT Bearer Flow.

Let’s start with creating a certificate that will be used for signing our JWT tokens:

  1. Go to Setup -> Certificate and Key Management
  2. Click “Create Self-Signed Certificate”
  3. Set following details:
    1. Label = “Magic Link Authentication”
    2. Unique Name = “Magic_Link_Authentication”
    3. Key Size = 4096 
  4. Click Save
  5. Click Download Certificate, we’ll need it during Connected App creation in next step

Next, we’ll create Connected App:

  1. Go to Setup -> Apps -> App Manager
  2. Click New Connected App
  3. Set following details:
    1. Connected App Name  =  “Magic Link Authentication”
    2. API Name = “Magic_Link_Authentication”
    3. Contact Email = your desired email address
    4. Enable OAuth Settings = checked
    5. Callback URL = https://login.salesforce.com
    6. Use digital signatures = checked and upload certificate created as part of previous step
    7. Selected OAuth Scopes = api, web, refresh_token, offline_access, openid
  4. Click Save
  5. Click Manage on our newly created Connected App
  6. Click Edit Policies
  7. Set Permitted Users = Admin approved users are pre-authorized
  8. Click Save

In order for users to be able to authenticate via this Connected App we need to give them access. It is best done via assigning Permission Sets with access to Connected App to users.

Setup is complete, now we can easily generate JWT’s for given user in Apex Class:

				
					private static String getJwtForUser(String username) {
	Auth.JWT jwt = new Auth.JWT(); 
    jwt.setSub(username); 
    jwt.setAud(COMMUNITY_URL);
    jwt.setIss(CLIENT_ID); //Client Id of Connected App created in previous step
    jwt.setValidityLength(VALIDITY); //How long this jwt should be valid for

    //You could also add additiona information with jwt.setAdditionalClaims() which will also be signed and secured

    Auth.JWS jws = new Auth.JWS(jwt.toJSONString(), CERT); //Certificate Unique Name created in previous step

    return jws.getCompactSerialization();
}

				
			

Magic Link Endpoint

Now that we have a process of creating JWT’s, we need an endpoint that will accept it and return a valid frontdoor.jsp URL. We can’t use standard /services/oauth2/token because it accepts only POST requests and we won’t be able to do that from Email.

Lets create an LWC that will process incoming parameters and trigger redirect to frontdoor.jsp:

				
					import { LightningElement, wire } from 'lwc';
import processMagicLink from "@salesforce/apex/MagicLinkController.processMagicLink";
import { CurrentPageReference } from "lightning/navigation";

export default class MagicLink extends LightningElement {

    @wire(CurrentPageReference)
    currentPageReference;

    handleRedirect() {
        processMagicLink({ 
            jwt: this.currentPageReference.state['token'],
            retURL: this.currentPageReference.state['retURL']
        }).then(result => {
            window.open(result, "_self");
        }).catch(err => {
            console.log(err)
        });
    }
}


				
			

And Apex Controller method for it:

				
					@AuraEnabled
public static String processMagicLink(String jwt, String retURL) {
        //Post the JWT to token endpoint
        String accessToken = '';
        String tokenEndpoint =  Site.getBaseSecureUrl() + '/services/oauth2/token';
        
        String payload = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=' + jwt;
        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        req.setEndpoint(tokenEndpoint);
        req.setHeader('Content-type', 'application/x-www-form-urlencoded');
        req.setBody(payload);
        Http http = new Http();
        HTTPResponse res = http.send(req);
        
        // get said access token from the response
        if ( res.getStatusCode() == 200 ) {
            System.JSONParser parser = System.JSON.createParser(res.getBody());
            while (parser.nextToken() != null) {
                if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && (parser.getText() == 'access_token')) {
                    parser.nextToken();
                    accessToken = parser.getText();
                    break;
                }
            }
        }
        // present the access token to frontdoor to get a session
        if (String.isNotBlank(accessToken)) {
            return new PageReference(Site.getBaseSecureUrl() + '/secur/frontdoor.jsp?sid=' + accessToken + '&retURL=' + retURL);
        }

        throw new AuraHandledException('Invalid or Expired Magic Link');
}


				
			

Now we need to create a page in out Experience Site with public access:

  1. Go to Setup -> Feature Settings -> Digital Experiences -> All Sites -> Your Site -> Builder
  2. Create New Standard Blank Page
    1. Name = Magic Link
    2. URL = /magic-link
    3. API Name = magic_link
  3. From Components list add our newly created LWC component to the page
  4. Update Page Settings and set it access to Public:

5. Publish Site

We are all set now! You can now generate links in following structure and users will be auto-authenticated and redirected to desired page:

https://your-community-url.com/magic-link?token=JWT&retURL=RELATIVE_URL_TO_OPEN

About the Author
Nazim Aliyev, CTO @ Nubessom

Nazim Aliyev, CTO @ Nubessom

Salesforce Consultant & Architect, Business Applications, Business Processes and Technology Consulting, Entrepreneurship. More than 15 years working on the CRM and Salesforce Ecosystems.

Let´s talk about your challenge!

    In order to provide you the content requested, we need to store and process your personal data. If you consent to us storing your personal data for this purpose, please tick the checkbox below.


    You can unsubscribe from these communications at any time. For more information on how to unsubscribe, our privacy practices, and how we are committed to protecting and respecting your privacy, please review our Privacy Policy.

    Need more Inspiration? keep reading Our related content