azureoauth-2.0microsoft-graph-apibearer-tokenclientcredential

C# - Get Graph access token - using Client ID, Client Secret, Scope with Client Delegated Permissions


I have got the graph delegated permissions on my AAD app Client ID.

Now, I want to request access token for the graph calls using app Client ID, app Client Secret and Graph Scope in the backend without user consent.

I have tried the below approach but getting a Bad Request, can anyone guide me in the right way of what I'm doing wrong?

string graphAccessUrl = "https://login.microsoftonline.com/tenant.onmicrosoft.com/oauth2/v2.0/token";
    
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));
List<KeyValuePair<string, string>> values = new()
{
    new KeyValuePair<string, string>("grant_type", "client_credentials"),
    new KeyValuePair<string, string>("client_id", appClientId),
    new KeyValuePair<string, string>("client_secret", appClientSecret),
    new KeyValuePair<string, string>("scope", scope) //graph scope
};
HttpContent c = new FormUrlEncodedContent(values);
//GET Method  
try
{
    HttpResponseMessage response = _httpClient.PostAsync(new Uri(graphAccessUrl), c).Result;
    if (response.IsSuccessStatusCode)
    {
        string responseString = response.Content.ReadAsStringAsync().Result;
        TokenData reponseObj = JsonConvert.DeserializeObject<TokenData>(responseString);
        string accessToken = reponseObj.access_token;                            
        return accessToken;
    }
    else
    {                            
        throw new ArgumentException("Failed to get authtoken due response code." + response.StatusCode);
    }
}
catch (Exception ex)
{
    throw new ArgumentException(ex.Message);
}

Solution

  • Unless your scenario is a little different to mine, the usual approach is to exchange the current user's access token for a Graph access token, as in my code sample. My code is in Node.js but you'll be able to translate it to C# easily enough.

        *
        * Use the Azure specific 'on behalf of' flow to get a token with permissions to call the user info endpoint
        */
        private async _getGraphAccessToken(accessToken: string): Promise<string> {
    
            try {
    
                const formData = new URLSearchParams();
                formData.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
                formData.append('client_id', this._configuration.graphClient.clientId);
                formData.append('client_secret', this._configuration.graphClient.clientSecret);
                formData.append('assertion', accessToken);
                formData.append('scope', 'openid profile email');
                formData.append('requested_token_use', 'on_behalf_of');
    
                const options = {
                    url: this._configuration.tokenEndpoint,
                    method: 'POST',
                    data: formData,
                    headers: {
                        'content-type': 'application/x-www-form-urlencoded',
                        'accept': 'application/json',
                    },
                };
    
                const response = await axios.request(options as AxiosRequestConfig) as any;
                return response.data.access_token!;
    
            } catch (e) {
    
                // Report Graph errors clearly
                throw ErrorFactory.fromUserInfoTokenGrantError(e, this._configuration.tokenEndpoint);
            }
        }
    

    In OAuth terms this is a user assertion, to swap an incoming access token for another access token for the same user. Some further notes on setup in this blog post.