QR Code Login Prerequisites

Set up one-time token exchange, configure an Apex class, and set up a Visualforce page.

Set Up One-Time Token Exchange 

  1. Generate a self-signed certificate and name it JWT_Bearer. See Salesforce Help: Generate a Self-Signed Certificate.
  2. Download the certificate.
  3. Create a connected app and set its client ID to jwtClientId.
  4. Enable digital signatures. 
  5. Upload the certificate that you downloaded in step 2.
  6. In App Manager, go to Selected OAuth Scopes and add Manage user data via Web browsers (web) and Perform requests at any time (refresh_token, offline_access).
  7. In your connected app, go to Manage > Edit Policies > OAuth Policies and set the Permitted Users dropdown to Admin approved users are pre-authorized.

Generate Login URLs with an Apex Controller Class 

One of the requirements for QR code login is a properly configured Apex controller class. The Apex controller class generates the login URL by using the UI Bridge API and provides it to the Visualforce page before it’s encoded to the QR code. You can set up your Apex controller class to generate the login URL by using either the OAuth 2.0 hybrid web server flow or the OAuth 2.0 hybrid user-agent token flow. For more information on Apex, UI Bridge API, and the auth flow options, see:

To create an Apex class, complete these steps.

  1. In Salesforce Setup, search for and select Apex Classes
  2. Click New, and then fill in a name for your Apex Class.
  3. Click Save or Quick Save.

Configure Your Apex Class with Web Server Flow 

To configure your Apex class with web server flow, add this code sample in the Apex Class tab. Make sure to fill in the class’s placeholder variables with their corresponding values. For variable descriptions, see the code comments.

1public class QRCodeLoginController {
2
3   // The generated one-time-use hyperlink for the QR code payload.
4   public String qrCodeOneTimeLoginHyperlink {get;set;}
5  
6   // The self-signed certificate name.
7   private String selfSignedCertName = 'JWT_Bearer';
8
9   // The connected app client ID. 
10   // (From Setup, Manage Apps, Connected Apps, and Manage Consumer Details.)
11   private String jwtClientId = 'CONNECTED_APP_CLIENT_ID';
12
13   // The Salesforce instance URL.
14   private String instanceUrl = 'INSTANCE_URL';
15
16   // The Salesforce Identity API OAuth 2.0 token endpoint URL.
17   private String oauth2TokenEndpoint = instanceUrl + '/services/oauth2/token';
18
19   // The Salesforce Identity API OAuth 2.0 UI bridge single access endpoint URL.
20   private String oauth2SingleAccessEndpoint = instanceUrl + 
21   '/services/oauth2/singleaccess';
22
23   // The mobile app’s client ID.
24   // (From Setup, Manage Apps, Connected Apps, and Manage Consumer Details.)
25   private String mobileClientId = 'MOBILE_CLIENT_ID';
26  
27   // The connected app’s callback URL.
28   // (From Setup, Manage Apps, Connected Apps, and Manage Consumer Details.)
29   private String callbackURL = 'CALLBACK_URL';
30  
31   // The mobile app’s deep link URL.
32   // (Includes the custom scheme, host, and path 
33   // that the app is configured to be linked from)
34   private String mobileDeepLinkUrl = 'MOBILE_DEEP_LINK_URL';
35
36   /**
37    * Generate the QR code’s one-time-login hyperlink. The operation is
38    * asynchronous and the generated hyperlink is stored in 
39    * `qrCodeOneTimeLoginHyperlink`.
40    */
41   public PageReference generateQrCodeOneTimeLoginHyperlink() {
42
43       // Generate PKCE code verifier and code challenge.
44       String codeVerifier = generateCodeVerifier();
45       String codeChallenge = generateCodeChallenge(codeVerifier);
46      
47       // Generate the client start URL.
48       String clientStartUrl = '/services/oauth2/authorize' + 
49       '?response_type=code&client_id=' + encode(mobileClientId) + 
50       '&redirect_uri=' + encode(callbackURL) + '&code_challenge=' + 
51       encode(codeChallenge);
52      
53       // Generate the UI Bridge API Front Door URL.
54       String uiBridgeFrontDoorUrl = generateUiBridgeFrontDoorUrl(clientStartUrl);
55      
56       // Assemble the log in QR code’s JSON payload.
57       Map<String, String> jsonPayload = new Map<String, String>();
58       jsonPayload.put('frontdoor_bridge_url', uiBridgeFrontDoorUrl);
59       jsonPayload.put('pkce_code_verifier', codeVerifier);
60       String jsonPayloadString = JSON.serialize(jsonPayload);
61       String jsonPayloadUrlEncoded = EncodingUtil.urlEncode(
62        jsonPayloadString, 'UTF-8');
63
64
65       qrCodeOneTimeLoginHyperlink = mobileDeepLinkUrl + '?bridgeJson=' + 
66       jsonPayloadUrlEncoded;
67       return null;
68   }
69
70   /*
71    * Generates the UI Bridge API Front Door URL by using the provided client
72    * start URL.
73    */
74   private String generateUiBridgeFrontDoorUrl(String startUrl) {
75       String accessToken = getAccessToken();
76
77
78       Http http = new Http();
79       HttpRequest request = new HttpRequest();
80       request.setMethod('POST');
81
82
83       String url = oauth2SingleAccessEndpoint;
84       request.setEndpoint(url);
85
86
87       String body = 'redirect_uri=' + encode(startUrl);
88       request.setBody(body);
89
90
91       request.setHeader('Content-Type','application/x-www-form-urlencoded');
92       request.setHeader('Authorization','Bearer ' + accessToken);
93
94
95       HttpResponse response = http.send(request);
96       SingleAccessResponse singleAccessResponse = 
97       (SingleAccessResponse)JSON.deserialize(response.getBody(), 
98       SingleAccessResponse.class);
99
100
101       return singleAccessResponse.frontdoor_uri;
102   }
103
104
105   /*
106    * Gets the access token.
107    */
108   private String getAccessToken() {
109       Auth.JWT jwt = new Auth.JWT();
110       jwt.setSub(UserInfo.getUserName());
111       jwt.setAud('https://login.test1.pc-rnd.salesforce.com');
112       jwt.setIss(jwtClientId);
113
114
115       // Additional claims to set scope
116       Map<String, Object> claims = new Map<String, Object>();
117
118
119       // Create the object that signs the JWT bearer token with a hardcoded 
120       // certificate developer name for POC.
121       Auth.JWS jws = new Auth.JWS(jwt, selfSignedCertName);
122
123
124       // POST the JWT bearer token.
125       Auth.JWTBearerTokenExchange bearer = 
126       new Auth.JWTBearerTokenExchange(oauth2TokenEndpoint, jws);
127       String accessToken = bearer.getAccessToken();
128
129
130       return accessToken;
131   }
132
133
134   /*
135    * Generates a PKCE code verifier.
136    */
137   private String generateCodeVerifier() {
138       // Code verifier set up.
139       String codeVerifierCharacterSet = 
140       'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
141       Integer codeVerifierLength = 128;
142
143       // Generate code verifier string.
144       String codeVerifier = '';
145       for (Integer i = 0; i < codeVerifierLength; i++) {
146           Integer index = Math.mod(Math.abs(Crypto.getRandomInteger()), 
147           codeVerifierCharacterSet.length());
148           codeVerifier += codeVerifierCharacterSet.substring(index, index + 1);
149       }
150
151       // Encode code verifier string to Base64 spec.
152       codeVerifier = EncodingUtil.base64Encode(Blob.valueOf(codeVerifier));
153
154       // Encode code verifier Base64 to Base64 URL-safe spec.
155       codeVerifier = codeVerifier
156       .replace('+', '-').replace('/', '_').replace('=', '');
157       return codeVerifier;
158   }
159
160   /*
161    * Generates a PKCE code challenge from the provided code verifier.
162    */
163   private String generateCodeChallenge(String codeVerifier) {
164       // Generate code challenge string from code verifier.
165       Blob codeVerifierBlob = Blob.valueOf(codeVerifier);
166       Blob codeChallenge256Blob = Crypto.generateDigest('SHA-256', codeVerifierBlob);
167
168       // Encode code challenge string to Base64 spec.
169       String codeChallengeBase64Encoded = 
170       EncodingUtil.base64Encode(codeChallenge256Blob);
171
172       // Encode code challenge Base64 to Base64 URL-safe spec.
173       String codeChallengeBase64UrlSafeEncoded = 
174       codeChallengeBase64Encoded.replace('+', '-').replace('/', '_').replace('=', '');
175
176       return codeChallengeBase64UrlSafeEncoded;
177   }
178
179   /*
180    * URL encodes a string.
181    */
182   private String encode(String value) {
183       return EncodingUtil.urlEncode(value, 'UTF-8');
184   }
185
186   /*
187    * Encodes a given Base64 string to the Base64 URL-safe spec.
188    */
189   private String base64ToBase64UrlSafe(String base64Value) {
190     return base64Value.replace('+', '-').replace('/', '_').replace('=', '');
191   }
192
193   /*
194    * A class to model responses from the 
195    * Salesforce Identity OAuth 2.0 UI bridge single access endpoint.
196    */
197   private class SingleAccessResponse {
198       public string frontdoor_uri;
199   }
200}

Configure Your Apex Class with User-Agent Flow 

To configure your Apex class with user-agent flow, add this code sample in the Apex Class tab.

1public class QRCodeLoginController {
2   public String qrCodeOneTimeLoginHyperlink {get;set;}
3
4   //
5   // One Time Use Token Exchange
6   //
7
8   // The connected app client ID. 
9   // (From Setup, Manage Apps, Connected Apps, and Manage Consumer Details.)
10   private String jwtClientId = 'CONNECTED_APP_CLIENT_ID';
11
12   // The self-signed certificate name.
13   private String selfSignedCertName = 'JWT_Bearer';
14
15   // The Salesforce instance URL.
16   private String instanceUrl = 'INSTANCE_URL';
17
18   // The Salesforce Identity API OAuth 2.0 token endpoint URL.
19   private String tokenEndpoint = instanceUrl + '/services/oauth2/token';
20
21   // The Salesforce Identity API OAuth 2.0 UI bridge single access endpoint URL.
22   private String oneTimeTokenEndpoint = instanceUrl + 
23   '/services/oauth2/singleaccess';
24
25   //
26   // Mobile App Configuration
27   //
28
29   // The mobile app’s client ID.
30   // (From Setup, Manage Apps, Connected Apps, and Manage Consumer Details.)
31   private String mobileClientId = 'MOBILE_CLIENT_ID';
32
33   // The connected app’s callback URL.
34   // (From Setup, Manage Apps, Connected Apps, and Manage Consumer Details.)
35   private String callbackURL = 'CALLBACK_URL';
36  
37   // The mobile app’s deep link URL.
38   // (Includes the custom scheme, host, and 
39   // path that the app is configured to be linked from)
40   private String mobileDeepLinkUrl = 'MOBILE_DEEP_LINK_URL';
41
42
43   /**
44    * Generate mobile sign in link
45    *  The operation is asynchronous 
46    *  and the generated link is stored in qrCodeOneTimeLoginHyperlink
47    */
48   public PageReference generateQrCodeOneTimeLoginHyperlink() {
49
50       String mobileStartURL = '/services/oauth2/authorize' + 
51       '?response_type=hybrid_token&client_id=' + encode(mobileClientId) + 
52       '&redirect_uri=' + encode(callbackURL);
53
54       String bridgeUrl = this.generateBridgeUrl(mobileStartURL);
55       Map<String, String> jsonMap = new Map<String, String>();
56       jsonMap.put('frontdoor_bridge_url', bridgeUrl);
57       String jsonString = JSON.serialize(jsonMap);
58       String encodedJsonString = EncodingUtil.urlEncode(jsonString, 'UTF-8');
59
60       this.qrCodeOneTimeLoginHyperlink = 
61       mobileDeepLinkURL + '?bridgeJson=' + encodedJsonString;
62       return null;
63   }
64
65   private String generateBridgeUrl(String startURL) {
66       String accessToken = getAccessToken();
67
68       Http h = new Http();
69       HttpRequest req = new HttpRequest();
70       req.setMethod('POST');
71
72       String url = oneTimeTokenEndpoint;
73       req.setEndpoint(url);
74
75       String body = 'redirect_uri=' + encode(startURL);
76       req.setBody(body);
77
78       //Add Headers
79       req.setHeader('Content-Type','application/x-www-form-urlencoded');
80       req.setHeader('Authorization','Bearer ' + accessToken);
81
82       //Send Authorization Request
83       HttpResponse res = h.send(req);
84       oneTimeUseResponse otur = 
85       (oneTimeUseResponse)JSON.deserialize(res.getBody(), oneTimeUseResponse.class);
86
87       return otur.frontdoor_uri;
88   }
89
90   private String getAccessToken() {
91       Auth.JWT jwt = new Auth.JWT();
92       jwt.setSub(UserInfo.getUserName());
93       jwt.setAud('https://login.test1.pc-rnd.salesforce.com');
94       jwt.setIss(jwtClientId);
95
96       //Additional claims to set scope
97       Map<String, Object> claims = new Map<String, Object>();
98
99       //Create the object that signs the JWT bearer token, 
100       // hardcoded cert dev name for POC
101       Auth.JWS jws = new Auth.JWS(jwt, selfSignedCertName);
102
103       //POST the JWT bearer token
104       Auth.JWTBearerTokenExchange bearer = 
105       new Auth.JWTBearerTokenExchange(tokenEndpoint, jws);
106
107       String accessToken = bearer.getAccessToken();
108
109       return accessToken;
110   }
111
112   private String encode(String value) {
113       return EncodingUtil.urlEncode(value, 'UTF-8');
114   }
115
116   private class oneTimeUseResponse {
117       public string frontdoor_uri;
118   }
119}

Encode Login URLs to QR Codes with a Visualforce Page 

Your Visualforce page invokes the Apex controller class, receives the generated UI Bridge API login URL, then encodes it to a QR code for the app to scan.

For more information on Visualforce pages, see Creating Your First Page in the Visualforce Developer Guide.

To set up a Visualforce page for QR code login, follow these steps.

  1. In Salesforce Setup, search for and select Visualforce Pages.
  2. Click New, and then fill in a name for your Visualforce page.
  3. Click the Page Editor bar at the bottom of the browser, and then add this Visualforce markup.
1<apex:page controller="QRCodeLoginController">
2<div style="padding:70px">
3    
4    <script type='text/javascript' 
5    src='https://cdn.jsdelivr.net/gh/davidshimjs/qrcodejs/qrcode.min.js'> 
6    </script>
7    
8    <h1 style="font-size: large">Do you believe in magic?</h1>
9    
10    <div id="qrcode" style="margin-bottom: 10px; margin-top: 10px"></div>
11    
12    <div id="debug"></div>
13    
14    <apex:form >
15        <apex:commandButton action="{!generateQrCodeOneTimeLoginHyperlink}" 
16        value="Generate Mobile Sign-In URL" 
17        style="margin-left:44px; margin-top:10px; margin-bottom: 10px"/>
18        <br></br> 
19            <script type="text/javascript">
20                console.log("{!qrCodeOneTimeLoginHyperlink}"); 
21                qrText = "{!qrCodeOneTimeLoginHyperlink}";
22
23                new QRCode(document.getElementById("qrcode"), {
24                  text: qrText,
25                  width: 512,
26                  height: 512,
27                  colorDark : "#000000",
28                  colorLight : "#ffffff",
29                  correctLevel : QRCode.CorrectLevel.L 
30                  // Note, these QR codes may be about 2,000 characters long.
31                  // Higher correctness levels may render the QR code over 
32                  // the allowed data length and be unreadable.
33                });
34                document.getElementById("debug").innerHTML = qrText;                
35            </script>
36    </apex:form>
37</div>
38</apex:page>

If you changed any function names in the sample Apex code, make sure that your Visualforce page reflects the corresponding changes.

Note

We've Moved

Welcome to the new home of the Mobile SDK Developer Guide! For now, the Japanese guide can be found in PDF form.