The Authorization code flow with Proof Key for Code Exchange (PKCE) is an OAuth 2.0 authorization method used to implement user authentication. This page provides an overview of the flow and explains how to implement it.
This flow is an extension of the original Authorization code flow with added security measures. To learn about the base Authorization code flow, go to Authorization code flow.
This is the recommended OAuth 2.0 flow for user authentication in client applications.
Proof Key for Code Exchange (PKCE)
![Authorization code flow with PKCE](/documentation/static/9b70d69ba50f4930c8f4f833a637a809/4cdf7/flow-auth-code-pkce.png)
Proof Key for Code Exchange is an extension of the OAuth 2.0 specification that adds an extra layer of security using a locally generated code_
and code_
. This ensures that the client that requests an access token is the same client that requested an authorization code. This prevents attackers from intercepting an authorization code returned from the authorization endpoint and using it to gain system access.
The general steps for the authorization code flow with PKCE are:
-
Client generates a random
code_
andverifier code_
locally using SHA256 encryption.challenge -
Client sends a request to the authorization endpoint that includes the
code_
as well as achallenge client_
andid redirect_
from OAuth credentials.uri -
The application user signs in with an ArcGIS account. The authorization endpoint verifies the user identity and returns an authorization code.
-
Client uses the
authorization_
andcode code_
, as well as theverifier client_
andid redirect_
from OAuth credentials, to submit a request to the token endpoint. The token endpoint checks that theuri code_
aligns with the originalverifier code_
and issues an access token.challenge -
Client uses the access token to authorize requests to secure resources.
ArcGIS APIs and SDKs
Authorization code with Proof Key for Code Exchange (PKCE) is the default authorization flow used by all ArcGIS APIs and Maps SDKs. To learn how to implement this flow using an ArcGIS API,
Manual implementation
The remainder of this page shows how to manually implement user authentication without using an ArcGIS API or Maps SDK. Instead, authentication is implemented by directly making HTTP requests to the proper REST API endpoints.
This sample is written in JavaScript, but can be implemented in any language. It adheres to the OAuth 2.0 specification for the Authorization code with Proof Key for Code Exchange (PKCE) flow.
Create OAuth credentials
A set of OAuth credentials are required for user authentication. These credentials are created as an item in your organization's portal.
-
Sign in to your portal.
-
Click Content > My content > New item and select Developer credentials.
-
In the Credential types menu, select OAuth credentials.
-
Add a redirect URL to your OAuth credentials in the format
"https:
or//<server> [:port]/callback.html" http:
. For example, if you are running your application on//my-arcgis-app: /auth https:
add//localhost: 8080 https:
, to the list of redirect URLs. You will create the//localhost: 8080/callback.html callback.html
page later in this tutorial. -
In the Privileges and Item access menus, click Next. These properties do not apply to user authentication.
-
Review your selections and, when you are ready, click Generate credentials.
-
Sign in to your portal.
-
Click Content > My content > New item and select Developer credentials.
-
Add a redirect URL to your OAuth credentials in the format
"https:
or//<server> [:port]" http:
. For example, if you are running your application on//my-arcgis-app: /auth https:
add//localhost: 8080 https:
, to the list of redirect URLs. You will create the//localhost: 8080/callback.html callback.html
page later in this tutorial. -
In the Privileges and Item access menus, click Next. These properties do not apply to user authentication.
-
Review your selections and, when you are ready, click Generate credentials.
Configure authentication variables
-
Copy your client ID and chosen redirect URL from your OAuth credentials and include them in your app.
index.htmlUse dark colors for code blocks const clientId = '<YOUR_CLIENT_ID>'; const redirectUri = '<YOUR_REDIRECT_URL>'; let session = null; let map = null; const signInButton = document.getElementById('sign-in'); const signOutButton = document.getElementById('sign-out');
Generate a code verifier and challenge
-
Use language-specific libraries to generate a code verifier. This string is required in the "Authorization code with PKCE" flow, and will be sent to the token endpoint to request an access token.
index.htmlUse dark colors for code blocks const dec2hex = (dec) => { return ('0' + dec.toString(16)).substr(-2) } const generateRandomString = () => { var array = new Uint32Array(56/2); window.crypto.getRandomValues(array); return Array.from(array, dec2hex).join(''); } const codeVerifier = generateRandomString();
-
Generate a code challenge, a base64 SHA256 encrypted version of the code verifier. This string will be sent to the authorization endpoint to obtain an authorization code.
index.htmlUse dark colors for code blocks const dec2hex = (dec) => { return ('0' + dec.toString(16)).substr(-2) } const generateRandomString = () => { var array = new Uint32Array(56/2); window.crypto.getRandomValues(array); return Array.from(array, dec2hex).join(''); } const challengeFromVerifier = async (verifier) => { // Generate SHA256 buffer const encoder = new TextEncoder(); const data = encoder.encode(verifier); const sha256 = await window.crypto.subtle.digest('SHA-256', data); // Encode SHA256 as base 64 var str = ""; var bytes = new Uint8Array(sha256); var len = bytes.byteLength; for (var i = 0; i < len; i++) { str += String.fromCharCode(bytes[i]); } const challenge = btoa(str) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); return challenge; } const codeVerifier = generateRandomString(); const codeChallenge = await challengeFromVerifier(codeVerifier);
Request an authorization code
-
Format a GET request to the authorization endpoint. Include your
client_
,id redirect_
, anduri code_
.challenge index.htmlUse dark colors for code blocks const authorizationEndpoint = 'https://www.arcgis.com/sharing/rest/oauth2/authorize'+ '?client_id=' + clientId + '&code_challenge=' + codeChallenge + '&code_challenge_method=S256' + '&redirect_uri=' + window.encodeURIComponent(redirectUri)+ '&response_type=code'+ '&expiration=20160';
-
When the user clicks 'sign in', open the authorization endpoint in a new window.
index.htmlUse dark colors for code blocks const authorizationEndpoint = 'https://www.arcgis.com/sharing/rest/oauth2/authorize'+ '?client_id=' + clientId + '&code_challenge=' + codeChallenge + '&code_challenge_method=S256' + '&redirect_uri=' + window.encodeURIComponent(redirectUri)+ '&response_type=code'+ '&expiration=20160'; signInButton.addEventListener('click', () => window.open(authorizationEndpoint, 'oauth-window', 'height=400,width=600,menubar=no,location=yes,resizable=yes,scrollbars=yes,status=yes'));
Create a callback
-
Create a callback function in
index.html
that will receive the authorization code.index.htmlUse dark colors for code blocks const oauthCallback = (authorizationCode) => { } window.oauthCallback = oauthCallback; //required due to the module scope of this script
-
Create a new HTML page at the location of your redirect URI. When a user successfully authenticates at the authorization endpoint, they will be redirected to this page.
callback.htmlUse dark colors for code blocks <!DOCTYPE html> <head> <title>ArcGIS user authentication OAuth 2.0 callback (vanilla JS)</title> </head> <body> <script type="module"> </script> </body> </html>
-
Access the authorization code returned from the endpoint. It is found in the
search
parameter of the URL. Pass the code to the callback function created inindex.html
.callback.htmlUse dark colors for code blocks <!DOCTYPE html> <head> <title>ArcGIS user authentication OAuth 2.0 callback (vanilla JS)</title> </head> <body> <script type="module"> const match = (window.location.search) ? window.location.search.match(/\?code=([^&]+)/) : false; // if we found an authorization code in the URL, pass the token up to a global function in index.html if(match[1]) { window.opener.oauthCallback(match[1]); } window.close(); </script> </body> </html>
Request an access token
-
Find the URL of the token endpoint for your portal service. For ArcGIS Online users, the token endpoint is
https:
.//www.arcgis.com/sharing/rest/oauth2/token index.htmlUse dark colors for code blocks const oauthCallback = (authorizationCode) => { const tokenEndpoint = 'https://www.arcgis.com/sharing/rest/oauth2/token'; } window.oauthCallback = oauthCallback; //required due to the module scope of this script
-
Submit an HTTP request to the token endpoint to request an access token. Include your
authorization_
,code client_
,id redirect_
, anduri code_
to create a valid request, and set theverifier grant_
astype authorization_
.code index.htmlUse dark colors for code blocks const oauthCallback = (authorizationCode) => { const tokenEndpoint = 'https://www.arcgis.com/sharing/rest/oauth2/token'; fetch(tokenEndpoint, { method:'POST', body: JSON.stringify({ client_id:clientId, grant_type:'authorization_code', code:authorizationCode, redirect_uri:redirectUri, code_verifier:codeVerifier }), headers: { "Content-type": "application/json; charset=UTF-8" } }) } window.oauthCallback = oauthCallback; //required due to the module scope of this script
-
Access the response from the endpoint. If the request was valid, the response will contain an
access_
,token refresh_
,token username
, and other session information.index.htmlUse dark colors for code blocks const oauthCallback = (authorizationCode) => { const tokenEndpoint = 'https://www.arcgis.com/sharing/rest/oauth2/token'; fetch(tokenEndpoint, { method:'POST', body: JSON.stringify({ client_id:clientId, grant_type:'authorization_code', code:authorizationCode, redirect_uri:redirectUri, code_verifier:codeVerifier }), headers: { "Content-type": "application/json; charset=UTF-8" } }) .then(response => response.json()).then(newSession => { updateSession(newSession); initApp(newSession); }) } window.oauthCallback = oauthCallback; //required due to the module scope of this script
Serialize session info
-
Serialize the session information from the token endpoint and add it to local storage to make the session persistent across page refreshes.
index.htmlUse dark colors for code blocks Copy const updateSession = (sessionInfo) => { const userInfo = document.getElementById('user-info'); if (!sessionInfo) { localStorage.removeItem("__ARCGIS_USER_SESSION__"); session = null; destroyApp(); // signed out sidebar state userInfo.classList.add('hide'); userInfo.innerHTML = ``; signOutButton.classList.add('hide'); signInButton.classList.remove('hide'); } else { session = sessionInfo; localStorage.setItem("__ARCGIS_USER_SESSION__", JSON.stringify(session)); // signed in sidebar state userInfo.classList.remove('hide'); userInfo.innerHTML = `Welcome, ${sessionInfo.username}.`; signOutButton.classList.remove('hide'); signInButton.classList.add('hide'); } }