[SalesForce] Custom REST controller to access Auth.AuthToken.getAccessToken returning null

Following on from a previous question:

I've created a custom REST endpoint which proxies access to Auth.AuthToken.getAccessToken(authProviderId, providerName); as suggested by @identigral.

Apex REST controller class using API v49.0

@RestResource(urlMapping='/AccessToken/*')
global with sharing class AccessTokenRESTController {
    @HttpGet
    global static void getAccessToken() {
        try {
            String token = Auth.AuthToken.getAccessToken(authProviderId, providerName);

            System.debug(token);

            if (token == null) {
                RestContext.response.statusCode = 404;
                RestContext.Response.responseBody = Blob.valueOf('Unable to find token for authProviderId: ' + authProviderId);
                //return; // commented out for debugging
            }

            AccessTokenInfo info = new AccessTokenInfo();
            info.AuthProviderId = authProviderId;
            info.AccessToken = token;

            RestContext.Response.addHeader('Content-Type', 'application/json');
            RestContext.Response.statusCode = 200;
            RestContext.Response.responseBody = Blob.valueOf(JSON.serializePretty(info));
        } catch (Exception error) {
            System.debug(LoggingLevel.ERROR, error);
            throw error;
        }
    }

    public class AccessTokenInfo {
        public String Name {
            get {
                return UserInfo.getName();
            }
        }
        public String Username {
            get {
                return UserInfo.getUsername();
            }
        }
        public String AccessToken { get; set; }
        public String AuthProviderId { get; set; }
    }

    private static String authProviderId {
        get {
            List<AuthProvider> providers = [SELECT Id FROM AuthProvider WHERE DeveloperName = :authProviderDeveloperName];

            if (providers.size() == 0) {
                throw new CustomException('Unable to find an Auth. Provider with DeveloperName: ' + authProviderDeveloperName);
            }

            System.debug(providers);

            return providers[0].Id;
        }
    }

    private static String authProviderDeveloperName {
        get {
            return 'MockLab';
        }
    }

    private static String providerName {
        get {
            return 'Open ID Connect';
        }
    }
}

The REST controller is accessible via a Site and I am passing a bearer token when I call the endpoint.

The Auth. Provider configuration is:

  • Provider Type: Open ID Connect
  • Name: MockLab
  • URL Suffix: MockLab
  • Consumer Key: mocklab_oauth2
  • Consumer Secret: whatever
  • Authorize Endpoint URL: http://weyz3.mocklab.io/oauth/authorize
  • Token Endpoint URL: http://weyz3.mocklab.io/oauth/token
  • User Info Endpoint URL: blank
  • Token Issuer: blank
  • Default Scopes: blank
  • Send access token in header: true
  • Send client credentials in header: false
  • Include Consumer Secret in API Responses: true
  • Custom Error URL: blank
  • Custom Logout URL: blank
  • Registration Handler: blank
  • Execute Registration As: blank
  • Portal: blank
  • Icon URL: blank

The NamedCredential configuration is:

  • Label: MockLab
  • Name: MockLab
  • URL: https://weyz3.mocklab.io/
  • Certificate: blank
  • Identity Type: Named Principal
  • Authentication Protocol: OAuth 2.0
  • Authentication Provider: MockLab
  • Scope: openid, profile, email
  • Authentication Status: Authenticated
  • Generate Authorization Header: true
  • Allow Merge Fields in HTTP Header: false
  • Allow Merge Fields in HTTP Body: false
  • Outbound Network Connection: false

I've completed the OAuth flow, so the NamedCredential has an Authentication Status of Authenticated.

I've also tested the NamedCredential works by connecting to the external API and fetching some data using Apex.

But when I call my custom REST endpoint to get the access token, I always get a null value.

Example response:

{
  "Username" : "xxx@xxx.com",
  "Name" : "Admin User",
  "AuthProviderId" : "0SO4K000000GozdWAC",
  "AccessToken" : null
}

Based on the response, I can see the request has successfully authenticated using the bearer token.

But the call to Auth.AuthToken.getAccessToken(authProviderId, providerName); is still returning null

If I try running this code in the developer console:

String token = Auth.AuthToken.getAccessToken('0SO4K000000GozdWAC', 'Open ID Connect');
System.debug(token);

I get a null value.

I've tried calling the method via a LWC component:

@AuraEnabled(cacheable=true)
public static String getAccessToken() {
    return Auth.AuthToken.getAccessToken('0SO4K000000GozdWAC', 'Open ID Connect');
}

I get a null value.

Everything is done using the same Admin user.

UPDATE: I've changed the Auth. Provider and Named Credenital details to the actual values for a MockLab environment I have set up, so you can test using exactly the same information/setup I am using.

Questions

  1. What am I doing wrong?
  2. How can I fix it, so the controller returns the token?

Best Answer

Edited

I did some tests with your code and setup in a Developer Org,

enter image description here


Test 1: Log in as the user who created the Auth. Provider in Workbench

Result: I can get the token in workbench.

enter image description here


Test 2: Grant apex class access to a site and visit the site

Result: I got a null as a site user.

enter image description here


Test 3: Log in as another System Admin in Workbench

Result: I got a null

enter image description here


I guess only the user created the Auth. Provider can get the token using Auth.AuthToken.getAccessToken(authProviderId, providerName);

Can you confirm that the username in your sample response is the same as the username of the Salesforce Admin that created the Auth.Provider?


I created a Connected App and got a token of the current user

enter image description here

then I used the user token to call the rest api and managed to get the AccessToken in Postman.

enter image description here

While it is possible to successfully get the token, this introduces a new token.

Related Topic