Issues with Microsoft Graph API and MSAL Authentication Flow in C#

Florian Weidinger 0 Reputation points
2025-02-10T11:51:34.4866667+00:00

I'm experiencing challenges with the authentication flow for my SaaS application written in C#. The goal is to implement a secure and efficient process allowing users to log in and authorize my backend to access their Microsoft contacts.

Here’s the desired workflow:

  1. The web application (JavaScript) calls a REST API to retrieve a Microsoft login URI for user authentication.
  2. After successfully logging in, the user is redirected to a callback function that stores necessary information (like tokens) in my database for backend use.
  3. A Hangfire job is set up to periodically fetch the user's contact information from the Microsoft Graph API.
  4. If the access token expires, the Hangfire job should refresh it automatically without requiring user interaction, ensuring that users only need to authorize once.

Currently, I have a partially functional flow, but I suspect it deviates from best practices and I have encountered some issues.

Here are some relevant code snippets:

Redirect User to Login URI

var authorizationUrl = new UriBuilder($"https://login.microsoftonline.com/{_tenantId}/oauth2/v2.0/authorize")
{
    Query = $"client_id={Uri.EscapeDataString(_clientId)}&" +
            $"redirect_uri={Uri.EscapeDataString(_redirectUri)}&" +
            $"response_type=code&" +
            $"scope={Uri.EscapeDataString(_scopes)}&" +
            $"login_hint={Uri.EscapeDataString(loginHint)}&" +
            $"state={Uri.EscapeDataString(id.ToString())}"
};

return Redirect(authorizationUrl.ToString());

Callback to Store Token

I've run into an issue where the access token returned is invalid; only the id_token seems to work, but it doesn’t appear suitable for future access.

var app = ConfidentialClientApplicationBuilder.Create(_clientId)
    .WithRedirectUri(_configuration["MicrosoftOAuth:RedirectUri"])
    .WithClientSecret(_clientSecret)
    .WithAuthority($"https://login.microsoftonline.com/{_tenantId}")
    .Build();

var scopes = _scopes.Split(' ');
var result = await app.AcquireTokenByAuthorizationCode(scopes, code).ExecuteAsync();

var accessToken = result.IdToken;

if (string.IsNullOrEmpty(accessToken))
{
    _logger.LogError("Failed to retrieve access token.");
    return BadRequest("Failed to retrieve access token.");
}

remoteConnection.AccessToken = accessToken;
remoteConnection.AccessTokenExpiry = result.ExpiresOn.UtcDateTime;
await _dbContext.SaveChangesAsync();

return Ok(new { AccessToken = accessToken, Expiry = remoteConnection.AccessTokenExpiry });

Function Called by Hangfire Job to Get Contact Data

var token = remoteConnection.AccessToken;
var onBehalfOfCredential = new OnBehalfOfCredential(_tenantId, _clientId, _clientSecret, token);
var graphClient = new GraphServiceClient(onBehalfOfCredential);

List<Microsoft.Graph.Models.Contact> allContacts = new List<Microsoft.Graph.Models.Contact>();

var contactPage = await graphClient.Users[remoteConnection.Username].Contacts.GetAsync(requestConfig =>
{
    requestConfig.QueryParameters.Top = 500;
});

I have not found adequate documentation or examples to guide me through these issues, so I would appreciate any assistance. Thank you!

Microsoft Security Microsoft Graph
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Anonymous
    2025-02-11T02:11:22.7533333+00:00

    Hello Florian Weidinger,

    Thank you for reaching out to Microsoft Support!

    From your problem description, it is clear that you are using Auth code flow when obtaining tokens.

    To get a token using Auth code flow, you need to perform an interactive user login to get authorization_code first, and you have no problems here.

    Then when you use the value of code to get the access token, you have a problem, the token is invalid, and the details of the request token are not shown in your code snippet, so we recommend that you refer to the request for the token in this document, as follows:

    // Line breaks for legibility only
    POST /{tenant}/oauth2/v2.0/token HTTP/1.1
    Host: https://login.microsoftonline.com
    Content-Type: application/x-www-form-urlencoded
    client_id=11112222-bbbb-3333-cccc-4444dddd5555
    &scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
    &code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
    &redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
    &grant_type=authorization_code
    &code_verifier=ThisIsntRandomButItNeedsToBe43CharactersLong 
    &client_secret=sampleCredentia1s    // NOTE: Only required for web apps. This secret needs to be URL-Encoded.
    

    Please refer to the code to obtain the token. At the same time, after obtaining the token, please parse the token in jwt.ms to check whether the token has the permission you need.

    Hope this helps.

    If the answer is helpful, please click Accept Answer and kindly upvote it. If you have any further questions about this answer, please click Comment.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.