-
Notifications
You must be signed in to change notification settings - Fork 343
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add public API for long-running OBO methods with custom cache key and do not use refresh token for normal OBO #2820
Conversation
src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Cache/Items/MsalRefreshTokenCacheItem.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
Outdated
Show resolved
Hide resolved
@pmaytak, as you said, there is a risk that the API is miss used I think we might want to keep AcquireTokenOnBehalfOf as is today, and add 2 new methods:
Both methods could return Example of usage: This method initiates the long running process. /// <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 = null;
cca.InitiateLongRunningProcessInWebAPI(scopes, jwt, ref key);
// 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
} And here is the callback controller 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 IConfidentialClientApplication cca;
/* constructor omitted */
// GET: api/values
// [RequiredScope("access_as_user")]
[HttpGet]
[AllowAnonymous]
public async Task GetAsync(string key)
{
var result = await cca.AcquireTokenInLongRunningProcess(new string[] { "user.read" }, key).ConfigureAwait(false);
}
}
}
} |
@jmprieur Tests are in OboRequestTests. (Test) for Should we specifically mention OBO in the method names to make it more clear? Something like |
src/client/Microsoft.Identity.Client/IConfidentialClientApplication.cs
Outdated
Show resolved
Hide resolved
if (requestParams.UserAssertion == null && !string.IsNullOrEmpty(requestParams.OboCacheKey) && tokenCacheItems.Count == 0) | ||
{ | ||
logger.Error("[FindAccessTokenAsync] AcquireTokenInLongRunningProcess was called and OBO token was not found in the cache."); | ||
throw new MsalClientException(MsalError.OboCacheKeyNotInCacheError, MsalErrorMessage.OboCacheKeyNotInCache); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this throw an MsalUiRequiredException
instead? CC @jmprieur for exception behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im not sure it needs to, it may be that the developer simply hasnt supplied the already acquired userAssertion using InitiateLongRunningProcess
right?
src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs
Outdated
Show resolved
Hide resolved
tests/Microsoft.Identity.Test.Unit/CacheTests/CacheSerializationTests.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comment
86abd07
to
48a2f5a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, main comment is around integration test
tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/OboTests2.cs
Show resolved
Hide resolved
/// <param name="scopes">Scopes requested to access a protected API</param> | ||
/// <param name="userToken">A JSON Web Token which was used to call the web API and contains the credential information | ||
/// about the user on behalf of whom to get a token.</param> | ||
/// <param name="longRunningProcessSessionKey">Key by which to look up the token in the cache. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmaytak - let's add sid
claim as the suggested key to use, and add a remark explaining that MSAL cannot read tokens on its own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bgavrilMS is it correct to read this as "MSAL performs the OBO without validating who the token is for"? I'd suggest we vet this path for confused deputy attack surface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hpsin - MSAL doesn't look at incoming token at all. It's up to the web api to perform any validations. I believe Ms.Id.Web does some of that.
I may not understand the question, but allow me to elaborate.
Today, we advise people wanting to implement the "long running process OBO" scenario to keep around the incoming_assertion
. This is to avoid the "confused deputy" attack, i.e. the AT / RT from OBO can only be retrieved if the original assertion is found. However, this is pretty complicated, and we end up asking folks to keep around an expired JWT. They mostly keep it around in their own cache as "username" -> "original_jwt".
AcquireTokenInLongRunningProcess(scopes, key)
aims to fix that. It is a slightly more flexible version of AcquireTokenOnBehalfOf(scopes, incomming_assertion)
, to allow the web api to let go of the expired incomming_assertion and to remember only the sid
. Internally, MSAL will locate the AT / RT in its caches by this key.
Shall we meet to discuss this in more detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I think that answers my question in general. Do we have any kind of ratchet in place that tries to ensure that the web api has performed validation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pmaytak - let's add
sid
claim as the suggested key to use, and add a remark explaining that MSAL cannot read tokens on its own.
We cannot crack-open the token, so the claim would have to be provided by the application to MSAL.NET. Also, sid
is an optional claim, This requires an aka.ms link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comments, Logic looks good overall
one more thing, is there a test that validates that the normal OBO flow removes refresh token from the token response? |
This one tests that if AT is expired, OBO call does OBO flow and not RT flow. Line 74 in b4e8fbd
This one tests that when AT is returned from cache, there are not RT tokens in cache. Line 156 in b4e8fbd
In contrast, this tests that RT exists for long-running OBO. Line 187 in b4e8fbd
@trwalke Should I add some other tests? |
Should we really throw exceptions if
CC @jmprieur for opinion here. |
…Initiate is called with already existing key. Update tests.
@bgavrilMS Removed that exception in 627b4d9, returns the token now. |
tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/OboTests.cs
Outdated
Show resolved
Hide resolved
tests/Microsoft.Identity.Test.Integration.netfx/HeadlessTests/OboTests.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to improve a few XLM comments, otherwise LGTM
tested with Identity.Web and an id.web dev sample: AzureAD/microsoft-identity-web#1523
src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs
Outdated
Show resolved
Hide resolved
* execising the MSAL long running API * Fixing a couple of things * Updating to fixed MSAL code. See AzureAD/microsoft-authentication-library-for-dotnet#2820 Commit: AzureAD/microsoft-authentication-library-for-dotnet@37280d5 * Updating to MSAL.NET * Update src/Microsoft.Identity.Web/TokenAcquisitionOptions.cs Co-authored-by: jennyf19 <jeferrie@microsoft.com>
Fixes #2733.
Changes proposed in this request
InitiateLongRunningProcessInWebApi
that allows to specify a cache key which will be used for searching for an OBO access and refresh tokens instead of a user assertion hash as usual.AcquireTokenInLongRunningProcess
to retrieve the token from the cache with that key without providing a user assertion.ILongRunningWebApi
.Variations of assertions and keys for different OBO methods:
Logic flow:
InitiateLongRunningProcessInWebApi (scopes, assertion, key)
AcquireTokenInLongRunningProcess (scopes, key)
Testing
Manual tests. Added tests for new public API flows. Tests for when calling both, normal and long-running OBO methods. Tests that suggested cache expiry should work for normal OBO. Tests that normal OBO should not have a refresh token and should not use that flow.
Performance impact
Less memory used for normal OBO since we don't cache RT anymore.