-
Notifications
You must be signed in to change notification settings - Fork 241
get token in event handler
Jean-Marc Prieur edited this page Jun 23, 2021
·
9 revisions
Sometimes your web API will do long running processes on behalf of the user (think of OneDrive which creates albums for you). To achieve that, the idea is to:
- Get the access token that was used in the web API.
- Use this token to call the web API itself (in the example below a different controller after the callback
Controllers/HomeController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using TodoListService.Models;
namespace TodoListService.Controllers
{
[Authorize]
[Route("api/[controller]")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : Controller
{
private readonly ITokenAcquisition _tokenAcquisition; // do not remove
// The Web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API
// In-memory TodoList
private static readonly Dictionary<int, Todo> TodoStore = new Dictionary<int, Todo>();
ILongRunningProcessContextFactory _longRunningProcessAssertionCache;
public TodoListController(
IHttpContextAccessor contextAccessor,
ITokenAcquisition tokenAcquisition,
ILongRunningProcessContextFactory longRunningProcessAssertionCache)
{
_tokenAcquisition = tokenAcquisition;
_longRunningProcessAssertionCache = longRunningProcessAssertionCache;
// Pre-populate with sample data
if (TodoStore.Count == 0)
{
TodoStore.Add(1, new Todo() { Id = 1, Owner = $"{contextAccessor.HttpContext.User.Identity.Name}", Title = "Pick up groceries" });
TodoStore.Add(2, new Todo() { Id = 2, Owner = $"{contextAccessor.HttpContext.User.Identity.Name}", Title = "Finish invoice report" });
}
}
// GET: api/values
// [RequiredScope("access_as_user")]
[HttpGet]
public async Task<IEnumerable<Todo>> GetAsync()
{
string owner = User.GetDisplayName();
// Below is for testing multi-tenants
var result = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read" }).ConfigureAwait(false); // for testing OBO
var result2 = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read.all" },
tokenAcquisitionOptions: new TokenAcquisitionOptions { ForceRefresh = true }).ConfigureAwait(false); // for testing OBO
RegisterPeriodicCallbackForLongProcessing();
// string token1 = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read" }, "7f58f645-c190-4ce5-9de4-e2b7acd2a6ab").ConfigureAwait(false);
// string token2 = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "user.read" }, "3ebb7dbb-24a5-4083-b60c-5a5977aabf3d").ConfigureAwait(false);
await Task.FromResult(0); // fix CS1998 while the lines about the 2 tokens are commented out.
return TodoStore.Values.Where(x => x.Owner == owner);
}The content of RegisterPeriodicCallbackForLongProcessing is the following:
/// <summary>
/// This methods the processing of user data where the web API periodically checks the user
/// date (think of OneDrive producing albums)
/// </summary>
private void RegisterPeriodicCallbackForLongProcessing()
{
// Get the token incoming to the web API - we could do better here.
string key = _longRunningProcessAssertionCache.CreateKey(HttpContext);
// Build the URL to the callback controller, based on the request.
var request = HttpContext.Request;
string url = request.Scheme + "://" + request.Host + request.Path.Value.Replace("todolist", "callback") + $"?key={key}";
// Setup a timer so that the API calls back the callback every 10 mins.
Timer timer = new Timer(async (state) =>
{
HttpClient httpClient = new HttpClient();
var message = await httpClient.GetAsync(url);
}, null, 1000, 1000 * 60 * 1); // Callback every minute
}Here is the second controller: Controllers/Callback.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using TodoListService.Models;
namespace TodoListService.Controllers
{
[Authorize]
[Route("api/[controller]")]
//[RequiredScope("access_as_user")]
public class CallbackController : Controller
{
private readonly ITokenAcquisition _tokenAcquisition;
private ILogger _logger;
ILongRunningProcessContextFactory _longRunningProcessAssertionCache;
public CallbackController(
IHttpContextAccessor contextAccessor,
ITokenAcquisition tokenAcquisition,
ILogger<CallbackController> logger,
ILongRunningProcessContextFactory longRunningProcessAssertionCache)
{
_tokenAcquisition = tokenAcquisition;
_logger = logger;
_longRunningProcessAssertionCache = longRunningProcessAssertionCache;
}
// GET: api/values
// [RequiredScope("access_as_user")]
[HttpGet]
[AllowAnonymous]
public async Task GetAsync(string key)
{
var request = HttpContext.Request;
string calledUrl = request.Scheme + "://" + request.Host + request.Path.Value + Request.QueryString;
_logger.LogWarning($"{DateTime.UtcNow}: {calledUrl}");
using (_longRunningProcessAssertionCache.UseKey(HttpContext, key))
{
var result = await _tokenAcquisition.GetAuthenticationResultForUserAsync(new string[] { "user.read" }).ConfigureAwait(false); // for testing OBO
_logger.LogWarning($"OBO token acquired from {result.AuthenticationResultMetadata.TokenSource} expires {result.ExpiresOn.UtcDateTime}");
// For breakpoint
if (result.AuthenticationResultMetadata.TokenSource == Microsoft.Identity.Client.TokenSource.IdentityProvider)
{
}
}
}
}
}- Home
- Why use Microsoft Identity Web?
- Web apps
- Web APIs
- Minimal support for .NET FW Classic
- Logging
- Azure AD B2C limitations
- Samples
- Certificates
- Managed Identity as Federated Credential
- Federated Credentials from other Identity Provider
- Extensibility: Bring your own credential
- Get client secrets from KeyVault
- Web apps
- Web app samples
- Web app template
- Call an API from a web app
- Managing incremental consent and conditional access
- Web app troubleshooting
- Deploy to App Services Linux containers or with proxies
- SameSite cookies
- Hybrid SPA
- Web APIs
- Web API samples
- Web API template
- Call an API from a web API
- Token Decryption
- Web API troubleshooting
- web API protected by ACLs instead of app roles
- gRPC apps
- Azure Functions
- Long running processes in web APIs
- Authorization policies
- Generic API
- Customization
- Logging
- Calling graph with specific scopes/tenant
- Multiple Authentication Schemes
- Utility classes
- Setting FIC+MSI
- Mixing web app and web API
- Deploying to Azure App Services
- Azure AD B2C issuer claim support
- Performance
- specify Microsoft Graph scopes and app-permissions
- Integrate with Azure App Services authentication
- Ajax calls and incremental consent and conditional access
- Back channel proxys
- Client capabilities