0
votes

Summary:

Our Universal Windows App single-tenant client uses an ASP.NET Web API 2 as a proxy for single-sign on for various Microsoft Office 365 APIs. We use Active Directory for server authentication and the on-behalf-of single sign-on model in our server to exchange tokens for the Office 365 APIs.

Problem:

We have updated a permission scope in Azure for the Office 365 API and the user is not prompted to authorize permission for the new scope, nor is the new scope appearing on NEW tokens. What needs to be done to DETECT and ALLOW our users to authorize new permission scopes?

Additional Details:

  • Our server is hosted in MSFT Azure App Services. I understand the manifest in Azure is auto-generated and does not need to be manually updated to reflect the updated permission scope?
  • When the user first logs into the UWP app, they consent to single sign-on permissions associated with the server (eg. Mail.ReadWrite, etc.) which works fine. However, the user consent prompt does not show up again, even after I’ve removed both the client and server apps from my list of consented to apps using
  • We use the WebTokenRequest and WebAuthenticationCoreManager libraries in the client to get the token for the server. I have also tried using WebAuthenticationBroker (which is not the correct method for our sign-on architecture) and the ADAL library in our client. None of these libraries are prompting for the updated permission.
  • I have also tried adding wtf.Properties.Add("prompt", "consent"); to our WebTokenRequest to force the user to reapprove permissions. This does not work.
  • I have also tried restarting the App Service in Azure. This does nothing.

UPDATED 11/10/16: Following is relevant code I've pulled from our app architecture which may help. Additionally, our server utilizes ADAL version 2.24.304111323.

In our UWP app:

public class AppAuth
{
    WebTokenRequestResult result;
    WebAccount acc;

    async Task<WebTokenRequestResult> GetTokenAsync(WebTokenRequestPromptType promptType = WebTokenRequestPromptType.Default)
    {
        var wtr = new WebTokenRequest(
            provider: "https://login.windows.net",
            scope: "",
            clientId: appClientId,
            promptType: promptType
        );
        wtr.Properties.Add("authority", "https://login.windows.net");
        wtr.Properties.Add("resource", azureWebsiteUrl);

        if (promptType != WebTokenRequestPromptType.ForceAuthentication)
        {
            result = (acc == null) ?
            await WebAuthenticationCoreManager.GetTokenSilentlyAsync(wtr) :
            await WebAuthenticationCoreManager.GetTokenSilentlyAsync(wtr, acc);
        }

        if (promptType == WebTokenRequestPromptType.ForceAuthentication ||
            result?.ResponseStatus == WebTokenRequestStatus.UserInteractionRequired)
        {
            result = (acc == null) ?
            await WebAuthenticationCoreManager.RequestTokenAsync(wtr) :
            await WebAuthenticationCoreManager.RequestTokenAsync(wtr, acc);
        }
        return result;
    }
}

In our server:

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        app.UseWindowsAzureActiveDirectoryBearerAuthentication(
            new WindowsAzureActiveDirectoryBearerAuthenticationOptions
            {
                Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
                TokenValidationParameters = new TokenValidationParameters
                {
                    SaveSigninToken = true,
                    ValidateIssuer = false,
                    ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
                }
            });
    }
}

public class TokenChange
{ 
    protected AdUser _user;
    private UserAssertion _assertion;
    private static string _aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
    private static string _tenant = ConfigurationManager.AppSettings["ida:Tenant"];
    private static string _clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string _appKey = ConfigurationManager.AppSettings["ida:AppKey"];
    private string _accessToken;
    public AuthenticationResult AuthResult { get; set; }
    public AdalException AuthException { get; set; }
    private string _emailAddress;
    private HttpClient _httpClient;

    public bool Authenticate()
    {
        _accessToken = null;

        if (ClaimsPrincipal.Current.Identity.IsAuthenticated)
        {
            var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext
                as System.IdentityModel.Tokens.BootstrapContext;
            if (bootstrapContext != null)
            {
                Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
                var upn = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn);
                var email = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email);
                var userName = upn != null ? upn.Value : email?.Value;

                _emailAddress = ClaimsPrincipal.Current.Identity.Name;
                var userNameClaim = ClaimsPrincipal.Current.FindFirst("name");
                _fullName = userNameClaim != null ? userNameClaim.Value : String.Empty;
                _accessToken = bootstrapContext.Token;
                _assertion = new UserAssertion(_accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
            }
        }
        return _accessToken != null;
    }

    public bool GetAccess(string apiResource)
    {
        bool gotAccess = false;
        AuthResult = null;
        AuthException = null;

        if (_accessToken != null || Authenticate())
        {
            ClientCredential clientCred = new ClientCredential(_clientId, _appKey);

            string authority = String.Format(CultureInfo.InvariantCulture, _aadInstance, _tenant);
            AuthenticationContext authContext = new AuthenticationContext(authority);
            bool retry = false;
            int retryCount = 0;

            do
            {
                retry = false;

                try
                {
                    AuthResult = authContext.AcquireToken(apiResource, clientCred, _assertion);
                }
                catch (AdalException ex)
                {
                    AuthException = ex;

                    if (ex.ErrorCode == "temporarily_unavailable")
                    {
                        retry = true;
                        retryCount++;
                        Thread.Sleep(500);
                    }
                    else
                    {
                        throw (ex);
                    }
                }
            } while ((retry == true) && (retryCount < 1));

            if (AuthResult != null && AuthResult.AccessToken != null)
            {
                gotAccess = true;
            }
        }
        return gotAccess;
    }
1
This seems a bit spammy at first, but I'm not flagging it as such because it clearly contains a programming question and does not appear to be promoting anything. I would advise editing it to make it look less spammy.RamenChef

1 Answers

0
votes

Based on the description, you were developing an single tenant application which calling the downstream web API(Office 365 API) in your web API.

If you were using the cache to acquire the token in your web API, it will not acquire the new token unless the token is expired. And in this scenario, there is no need to consent/reconsent to update the permission.

Please ensure that you web API is acquire the token from new request instead of cache. If you were using the DbTokenCache, you can clear the cache by deleting the token cache records in PerWebUserCaches table in the database.

Note

In the describing scenario above, since the downstream web API(Office 365 API) get the token using the token issued for your web API which require users sign-in. So only the delegated permission work in the scenario( scp claim in the token instead of roles).