Why is Microsoft Graph sending the wrong value for the validationToken in the Change Notification subscription validation step?

Viewed 52

Microsoft graph is sending a very strange value in the query string of a validation request when creating a Change Notification subscription. My subscription is created with the following code:

         public async Task<Subscription> SubscribeToUserChanges()
         {
             var subscription = new Subscription
             {
                 ChangeType = "created,updated",
                 NotificationUrl = UserChangesSubscriberUrl,
                 Resource = SubscriptionResources.AllUsers,
                 ExpirationDateTime = DateTimeOffset.UtcNow.AddDays(SubscriptionTimeConstants.UsersTimeInDays),
                 ClientState = UserChangesSubscriberVerificationSecret,
                 LatestSupportedTlsVersion = TlsVersions.V1_2
             };
             return await _graphClient.Subscriptions
                 .Request()
                 .AddAsync(subscription);
         }

The following log is from my Azure Function handling the validation step of a Change Notification subscription. The code producing it is further below.

2022-09-06T16:33:40.049 [Information] Executing '[redacted]' (Reason='This function was programmatically called via the host APIs.', Id=[redacted])
2022-09-06T16:33:40.049 [Information] C# HTTP trigger function processed a request.
2022-09-06T16:33:40.049 [Information] ?validationToken=Validation%3a+Testing+client+application+reachability+for+subscription+Request-Id%3a+[redacted]
2022-09-06T16:33:40.050 [Information] Validation: Testing client application reachability for subscription Request-Id: [redacted]
2022-09-06T16:33:40.092 [Error] Invalid validation token. Expected 'fwldU*******', but got 'Valid*******'
2022-09-06T16:33:40.147 [Information] Executed 'UserChangedSubscriber' (Succeeded, Id=[redacted], Duration=97ms)

As you can see in 3rd line of the log, the validationToken sent in the query string is some description microsoft graph seems to have included in the request instead of my validationToken/changeState. I cannot figure why.

        // Handles the change notifications
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            var secret = Environment.GetEnvironmentVariable(EnvVarNames.UserChangesSubscriberVerificationSecret, EnvironmentVariableTarget.Process)
                ?? throw new Exception($"Missing environment variable '{EnvVarNames.UserChangesSubscriberVerificationSecret}'");
            
            log.LogInformation(req.QueryString.Value);

            if (req.Query.TryGetValue("validationToken", out var validationToken))
            {
                log.LogInformation(validationToken);
                var token = validationToken.ToString();
                if (token.Length < 5)
                {
                    log.LogError($"Validation token {token} too short");
                    return new UnauthorizedResult();
                }

                // validation only
                if (token != secret)
                {
                    
                    log.LogError($"Invalid validation token. Expected '{secret.Substring(0,5)}*******', but got '{token.Substring(0,5)}*******'");
                    return new UnauthorizedResult();
                }
                else
                {
                    log.LogInformation("Subscription validation succeeded.");
                    return new ContentResult() { Content = validationToken, ContentType = "text/plain", StatusCode = 200 };
                }
            }
            // ...  Rest of function is ommitted as it does not apply to this issue
        }
1 Answers

Could you try the validation process with this example?

[HttpPost("your-route")]
[Consumes("text/plain")]
public IActionResult NotificationValidationToken([FromQuery] string validationToken)
{
    return Ok(validationToken);
}
Related