Missing org_id in Auth0 Device Authentication Flow

Overview

The Device Authentication Flow currently does not natively support the Organizations feature. The standard org_id claim is not automatically populated in the access token, nor can it be directly inserted within a Post-Login Action. This article provides two workarounds to associate an organisation ID with a token issued by this flow.

Applies To
  • Device Authorization Flow
  • Organizations
  • Actions (Post-Login and Token Exchange)
Cause

It is not possible to directly overwrite or insert the standard org_id claim using a Post-Login Action, as this is designated as a restricted claim by Auth0.

Solution

Depending on the application flexibility and the subscription plan, select one of the following approaches to associate an organization ID with tokens.

 

Option 1

Insert Organization via a Custom Claim (Post-Login Action)

This approach requires downstream applications to read a custom claim instead of the standard org_id claim.

  1. Determine the user's organization using a Post-Login Action.

  2. Insert the resulting organization ID into a namespaced custom claim (for example, https://<domain.com>/org_id).

NOTE: If the user is a member of multiple organizations, the Action logic requires specific rules to determine which organization ID to append.

 

Option 2

Populate Standard org_id via Token Exchange

This approach utilizes the Token Exchange Flow for applications that strictly require the standard org_id claim.

NOTE: This feature is available for B2B Professional and above (Enterprise) plans.

 

This approach is a two-step process that relies on Approach 1 to pass the necessary data to the Token Exchange trigger:

  1. Create a Post-Login Action to inject a custom claim. During the initial Device Flow, this Action captures the user's organization and attaches it to the access token as a custom claim.
/**
 * Handler that will be called during the execution of a PostLogin flow.
 *
 * @param {Event} event - Details about the user and the context in which they are logging in.
 * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
 */
exports.onExecutePostLogin = async (event, api) => {
  // 1. Ensure this logic only runs for the Device Authorization Flow
  if (event.transaction && event.transaction.protocol === 'oauth2-device-code') {
    
    // 2. Define your custom namespace
    const NAMESPACE = 'https://your-domain.com';
    
    // 3. Determine the target Organization ID.
    // NOTE: This logic depends on your specific use case. You might read this 
    // from user metadata (shown below) or use the Management API to query their orgs.
    const targetOrgId = event.user.app_metadata?.default_org_id; 
    
    // 4. Inject the custom claim into the Access Token
    if (targetOrgId) {
      api.accessToken.setCustomClaim(`${NAMESPACE}/org_id`, targetOrgId);
      console.log(`Injected custom org_id claim: ${targetOrgId}`);
    } else {
      console.log("No organization ID found for this user.");
    }
  }
};
  1. Create a Custom Token Exchange Action. The application uses the access token generated in Step 1 to initiate a Token Exchange Flow. This Action intercepts the request, reads the custom claim, and uses api.authentication.setOrganization() to issue a new token featuring the standard org_id claim.
/**
* Handler that will be called during the execution of a Custom Token Exchange.
*
* @param {Event} event - Details about the client request.
* @param {TokenExchangeAPI} api - Interface whose methods can be used to change the behavior of the custom token exchange.
*/

// TODO: Replace this mock function with your actual token validation logic.
// The implementation depends on the issuer, algorithm (e.g., RS256 vs HS256), 
// and format of your specific subject_token.
async function validateToken(subjectToken) {
  // Example structure:
  // 1. Verify token signature
  // 2. Validate standard claims (exp, iss, aud)
  // 3. Return the payload if valid, or an error if invalid
  
  // NOTE: This is a placeholder. You must implement strong validation here.
  return { 
      isValid: true, // or false based on your validation
      payload: { 
          sub: "auth0|user123", 
          "https://your-domain.com/org_id": "org_xyz" // Your target org ID
      }, 
      error: null 
  };
}

exports.onExecuteCustomTokenExchange = async (event, api) => {
  const subjectToken = event.transaction.subject_token;

  if (!subjectToken) {
    return api.access.deny("invalid_request", "Missing subject_token");
  }

  // 1. Verify and Decode the incoming subject_token
  const { isValid, payload, error } = await validateToken(subjectToken);

  if (!isValid) {
    console.error("Token verification failed:", error);
    return api.access.rejectInvalidSubjectToken("Invalid subject_token provided.");
  }

  // 2. Extract data from the validated payload
  const originalSub = payload.sub;
  
  // Ensure this namespace exactly matches what you set in your Post-Login Action
  const targetOrgId = payload['https://your-domain.com/org_id']; 

  // 3. Set the user context for the transaction
  api.authentication.setUserById(originalSub);
    
  // 4. Set the standard organization context if the custom claim exists
  if (targetOrgId) {
      api.authentication.setOrganization(targetOrgId);
      console.log(`Successfully exchanged token for org_id: ${targetOrgId}`);
  } else {
      console.log("No custom org_id claim found in the subject token.");
  }
};

Recommended content

No recommended content found...