Anti-Fogery token cannot be decrypted when deployed behind a load balancer - iis

I've setup AntiForgery tokens in my Asp.Net Core 1.0 site as follows:
services.AddAntiforgery(options =>
{
options.HeaderName = "X-XSRF-Token"; //Angular's default header name for sending the xsrf token
});
app.Use(next => context =>
{
//2 tokens are generated. A Cookie token, which goes in an ASP cookie called something like .AspNetCore.Antiforgery.****** and a request token. The request token gets passed in the http request
//headers back to the server when executing a POST and is validated agains't the cookie token. By taking the request token and putting it in an HTTP-Only cookie called XSRF-TOKEN, Angular will
//automatically take the cookie and add it to an X-XSRF-Token header for each request. The request token received in the header is then validated against the cookie token. In ConfigureServices() above,
//ASP's antiforgery system is setup to look for the request token in the X-XSRF-Token header. For form posts (i.e. not posts done by Angular), the form has a token associated with it that gets passed in the POST
// which is used in place of the header token
var tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
return next(context);
});
app.UseAntiForgeryValidation();
public class AntiForgeryValidation
{
private readonly RequestDelegate _next;
IAntiforgery _antiforgery;
private ILogger<AntiForgeryValidation> _logger;
public AntiForgeryValidation(RequestDelegate next, IAntiforgery antiforgery, ILoggerFactory loggerFactory)
{
_next = next;
_antiforgery = antiforgery;
_logger = loggerFactory.CreateLogger<AntiForgeryValidation>();
}
public async Task Invoke(HttpContext context)
{
//don't need to validate anti-forgery tokens for GET
if (string.Equals("POST", context.Request.Method, StringComparison.OrdinalIgnoreCase)
|| string.Equals("PUT", context.Request.Method, StringComparison.OrdinalIgnoreCase)
|| string.Equals("DELETE", context.Request.Method, StringComparison.OrdinalIgnoreCase))
{
await _antiforgery.ValidateRequestAsync(context);
}
await _next(context);
}
}
public static partial class MiddlewareExtensionMethods
{
public static void UseAntiForgeryValidation(this IApplicationBuilder builder)
{
builder.UseMiddleware<AntiForgeryValidation>();
}
}
In my dev environment, and when published to IIS, this works well. There is a load balancer in front of my site though and when I access it through the VIP provided by the load balancer my anti-forgery validation starts failing with the following errors:
{7f02de42-e781-4c27-a2e5-fce932f4b7a4} was not found in the key ring. Unprotect operation cannot proceed
and
An unhandled exception was thrown by the application.The antiforgery token could not be decrypted.
(more of the log is below).
I should also point out that even though there's a load balancer, there is only 1 web server behind it so there shouldn't be any issue with the requests being split between servers. If anyone has any idea why this would be occurring when accessed through the load balancer and not when accessed directly (via a hosts entry to bypass the load balancer) I would love some assistance.
Log
2016-11-01 15:28:01.3761|22|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingUnprotectOperationToKeyWithPurposes|Performing unprotect operation to key {4441518f-1b1d-49a8-b06a-29724cb692ae} with purposes ('C:\temp\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-01 15:28:01.4074|22|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingProtectOperationToKeyWithPurposes|Performing protect operation to key {4441518f-1b1d-49a8-b06a-29724cb692ae} with purposes ('C:\temp\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-01 15:28:01.4074|22|DEBUG|Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.GetAndStoreTokens|An antiforgery cookie token was reused.
2016-11-01 15:28:01.4230|22|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingUnprotectOperationToKeyWithPurposes|Performing unprotect operation to key {7f02de42-e781-4c27-a2e5-fce932f4b7a4} with purposes ('C:\temp\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-01 15:28:01.4230|22|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.KeyWasNotFoundInTheKeyRingUnprotectOperationCannotProceed|Key {7f02de42-e781-4c27-a2e5-fce932f4b7a4} was not found in the key ring. Unprotect operation cannot proceed.
2016-11-01 15:28:01.4230|22|ERROR|Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame`1.RequestProcessingAsync|Connection id "0HL02KLA3LKUJ": An unhandled exception was thrown by the application.The antiforgery token could not be decrypted.
2016-11-01 15:28:01.4230|22|INFO|Microsoft.AspNetCore.Hosting.Internal.HostingLoggerExtensions.RequestFinished|Request finished in 44.7391ms 200
2016-11-01 15:28:01.4230|22|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Connection.Microsoft.AspNetCore.Server.Kestrel.Internal.Http.IConnectionControl.End|Connection id "0HL02KLA3LKUJ" completed keep alive response.
Update
I've added
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(#"c:\temp\"));
and a key is persisted to c:\temp\ but I've still having the issue. The updated log is below. The key: {f5087f37-37e3-4e52-b40b-77e32a285f3e} is written to c:\temp and is selected as the default key, however there's still some mysterious key {4441518f-1b1d-49a8-b06a-29724cb692ae} that the app is trying to use to decrypt the anti-fogery token which can't be found.
2016-11-02 07:19:28.7357|1|DEBUG|Microsoft.AspNetCore.Hosting.Internal.HostingLoggerExtensions.Starting|Hosting starting
2016-11-02 07:19:28.8450|1|DEBUG|Microsoft.AspNetCore.Hosting.Internal.HostingLoggerExtensions.Started|Hosting started
2016-11-02 07:19:29.0326|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Connection.Start|Connection id "0HL035D0UCHEL" started.
2016-11-02 07:19:29.1107|3|INFO|Microsoft.AspNetCore.Hosting.Internal.HostingLoggerExtensions.RequestStarting|Request starting HTTP/1.1 POST http://apidev.brewster.ca/ application/x-www-form-urlencoded 218
2016-11-02 07:19:29.1263|3|DEBUG|Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware.ApplyForwarders|Parameter count mismatch between X-Forwarded-For and X-Forwarded-Proto.
2016-11-02 07:19:29.1732|3|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingUnprotectOperationToKeyWithPurposes|Performing unprotect operation to key {4441518f-1b1d-49a8-b06a-29724cb692ae} with purposes ('C:\webpublish\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-02 07:19:29.1888|3|DEBUG|Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository.ReadElementFromFile|Reading data from file 'c:\temp\key-f5087f37-37e3-4e52-b40b-77e32a285f3e.xml'.
2016-11-02 07:19:29.1888|3|DEBUG|Microsoft.Extensions.Logging.LoggingExtensions.FoundKey|Found key {f5087f37-37e3-4e52-b40b-77e32a285f3e}.
2016-11-02 07:19:29.2200|3|DEBUG|Microsoft.Extensions.Logging.LoggingExtensions.ConsideringKeyWithExpirationDateAsDefaultKey|Considering key {f5087f37-37e3-4e52-b40b-77e32a285f3e} with expiration date 2017-01-31 14:11:02Z as default key.
2016-11-02 07:19:29.2357|3|DEBUG|Microsoft.Extensions.Logging.LoggingExtensions.OpeningCNGAlgorithmFromProviderWithChainingModeCBC|Opening CNG algorithm 'AES' from provider '' with chaining mode CBC.
2016-11-02 07:19:29.2513|3|DEBUG|Microsoft.Extensions.Logging.LoggingExtensions.OpeningCNGAlgorithmFromProviderWithHMAC|Opening CNG algorithm 'SHA256' from provider '' with HMAC.
2016-11-02 07:19:29.2670|3|DEBUG|Microsoft.Extensions.Logging.LoggingExtensions.UsingKeyAsDefaultKey|Using key {f5087f37-37e3-4e52-b40b-77e32a285f3e} as the default key.
2016-11-02 07:19:29.2670|3|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.KeyWasNotFoundInTheKeyRingUnprotectOperationCannotProceed|Key {4441518f-1b1d-49a8-b06a-29724cb692ae} was not found in the key ring. Unprotect operation cannot proceed.
2016-11-02 07:19:29.2670|3|ERROR|Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.GetCookieTokenDoesNotThrow|An exception was thrown while deserializing the token.The antiforgery token could not be decrypted.
2016-11-02 07:19:29.2982|3|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingProtectOperationToKeyWithPurposes|Performing protect operation to key {f5087f37-37e3-4e52-b40b-77e32a285f3e} with purposes ('C:\webpublish\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-02 07:19:29.2982|3|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingProtectOperationToKeyWithPurposes|Performing protect operation to key {f5087f37-37e3-4e52-b40b-77e32a285f3e} with purposes ('C:\webpublish\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-02 07:19:29.3138|3|DEBUG|Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.GetAndStoreTokens|A new antiforgery cookie token was created.
2016-11-02 07:19:29.3296|3|DEBUG|VTR.Common.Utilities.Middleware.AntiForgeryValidation.Invoke|AntiForgeryCookie: [.AspNetCore.Antiforgery.2mRY6wjt_Lw, CfDJ8I9RQUQdG6hJsGopcky2kq4ayXKUx4xErDElnyDij0J31qSLHyt3oKyqJ1ocoRHYoIkK7WSpze9SVEzOan0LQTFs3SwwtvMUw_e6EUPvaPxWjH_1_pQ5DiT8hu7TM8UNWjjFT_XSkNZz-uBVHdh2CmY]
2016-11-02 07:19:29.3296|3|DEBUG|VTR.Common.Utilities.Middleware.AntiForgeryValidation.Invoke|XSRF cookie: CfDJ8I9RQUQdG6hJsGopcky2kq45_O5j5bzBP5QyxbzIKXaSbb8K04mez2Czsa_OdCYn84bvSz2v8M-nkN_O6yorN8qfyy4mV3HdGJV3BQgWSHSFrziJfQonBKmiF4fsrmpHVX7jA6iugirWL8yNJPO35xY
2016-11-02 07:19:29.3607|3|DEBUG|VTR.Common.Utilities.Middleware.AntiForgeryValidation.Invoke|Form Token: CfDJ8ELeAn-B5ydMouX86TL0t6R3a1YFIaw2K3Wf6xpsbQPGztGb2uACGCdnofeFKd-woGFkwTjWQEC4Qag1E2A2RCxtvry4DJAvPSZnbKZGMd248exLwt9CX1bhWsYj8Cs6N9MvEQ38gqEXmEArDXFYMM0
2016-11-02 07:19:29.3607|3|DEBUG|VTR.Common.Utilities.Middleware.AntiForgeryValidation.Invoke|Header Token:
2016-11-02 07:19:29.3768|3|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.PerformingUnprotectOperationToKeyWithPurposes|Performing unprotect operation to key {7f02de42-e781-4c27-a2e5-fce932f4b7a4} with purposes ('C:\webpublish\wizard', 'Microsoft.AspNetCore.Antiforgery.AntiforgeryToken.v1').
2016-11-02 07:19:29.3768|3|TRACE|Microsoft.Extensions.Logging.LoggingExtensions.KeyWasNotFoundInTheKeyRingUnprotectOperationCannotProceed|Key {7f02de42-e781-4c27-a2e5-fce932f4b7a4} was not found in the key ring. Unprotect operation cannot proceed.
2016-11-02 07:19:29.3768|3|ERROR|Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame`1.RequestProcessingAsync|Connection id "0HL035D0UCHEL": An unhandled exception was thrown by the application.The antiforgery token could not be decrypted.
2016-11-02 07:19:29.3768|3|INFO|Microsoft.AspNetCore.Hosting.Internal.HostingLoggerExtensions.RequestFinished|Request finished in 286.4265ms 200
2016-11-02 07:19:29.4233|3|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Connection.Microsoft.AspNetCore.Server.Kestrel.Internal.Http.IConnectionControl.End|Connection id "0HL035D0UCHEL" completed keep alive response.

The issue was caused by a caching problem on the load balancer. Once the cache was flushed the missing key problem went away

Related

JWT Token Storage

I have been going through some of my .NET Core2 services and adding some JWT authentication to them to provide some basic security.
I created a new ProvisioningService which has an endpoint that builds a token and returns it:
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_config["Jwt:Issuer"],
_config["Jwt:Issuer"],
claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
I altered one of my existing services (which I'll refer to as TestService) by adding AddAuthentication in the StartUp. The endpoint for this call has the [HttpPost(), Authorize] attributes. I deployed these changes to my Test server.
When I call TestService/api/updateSomething I am returned a 401 Unauthorized as expected. On my local machine, I create a new token via ProvisioningService/api/buildToken and add the token from the response to my TestService call via the Authorization header. To my surprise...this worked.
Why does my TestService (on a completely different server) view a token created on my local machine as a valid token and allow the call to work? I was expecting this to return the same 401 because I assumed this token was going to be invalid on my Test server. My inexperience with JWT is probably showing....but I am not understanding how these tokens are being stored/shared between servers.
I failed to understand that the token itself has what it needs to authorize itself after it is decrypted. This question is no longer needed.

Docusign PARTNER_AUTHENTICATION_FAILED The specified Integrator Key was not found or is disabled

I have a web application that I am trying to integrate with Docusign. I am using the java docusign client. I started down the path to use OAuth2, but then found out the refresh token expires and from what I am reading the user would have to authenticate again (user interaction each time). Normally refresh does not expire but access token does. Am I reading that correctly?
I want the user to authenticate once and for the app to be able to use that token without having to ask the user for access again. So I started looking at JWT and am not finding the documentation I need.
Using the JWT I can get the user info for the account. But can't make the call for templates.
apiClient.configureJWTAuthorizationFlow(folder + "509.cert", folder
+ "509.ppk", "account-d.docusign.com", IntegratorKey, account, 3600 );
com.docusign.esign.client.auth.OAuth.UserInfo userInfo =
apiClient.getUserInfo(apiClient.getAccessToken());
// **** This prints fine *****
System.out.println("UserInfoxxx: " + userInfo);
// **** Verified the url is demo *****
apiClient.setBasePath(userInfo.getAccounts().get(0).getBaseUri() +
"/restapi");
com.docusign.esign.api.TemplatesApi api = new
com.docusign.esign.api.TemplatesApi(apiClient);
try {
com.docusign.esign.model.EnvelopeTemplateResults resp =
api.listTemplates(account);
for ( com.docusign.esign.model.EnvelopeTemplateResult template :
resp.getEnvelopeTemplates() )
{
out.println("X " + template.getDescription() + " " +
template.getName() );
}
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
https://demo.docusign.net/restapi
com.docusign.esign.client.ApiException: Error while requesting server,
received a non successful HTTP code 400 with response Body: '{
"errorCode": "PARTNER_AUTHENTICATION_FAILED",
"message": "The specified Integrator Key was not found or is disabled.
Invalid account specified for user."}'
at com.docusign.esign.client.ApiClient.invokeAPI(ApiClient.java:929)
at
com.docusign.esign.api.TemplatesApi.listTemplates(TemplatesApi.java:2471)
at
com.docusign.esign.api.TemplatesApi.listTemplates(TemplatesApi.java:2409)
I am using my integrator key (client id)
Any suggestions or advise?
[Update]
Yes I am here is my flow.
Send the user to
https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=" + IntegratorKey + "&redirect_uri=" + redirectUri + "&state=" + state;
On redirect
String code = request.getParameter("code");
com.docusign.esign.client.auth.OAuth.OAuthToken oAuthToken = apiClient.generateAccessToken(IntegratorKey, ClientSecret, code);
System.out.println("OAuthToken: " + oAuthToken.getAccessToken() );
com.docusign.esign.client.auth.OAuth.UserInfo userInfo = apiClient.getUserInfo(oAuthToken.getAccessToken());
String folder = com.n.util.Settings.getInstance().getRealPath("/") + "META-INF/cert/";
apiClient.configureJWTAuthorizationFlow(folder + "509.cert", folder + "509.ppk", "account-d.docusign.com", IntegratorKey, userInfo.getSub(), oAuthToken.getExpiresIn() );
I then store userInfo.getSub() and use that in the other requests.
I have tried the account id as well.
Re: I started down the path to use OAuth2, but then found out the refresh token expires and from what I am reading the user would have to authenticate again (user interaction each time).
Normally refresh does not expire but access token does.
Am I reading that correctly?
Not completely. Please use Authorization Code Grant as you originally planned.
Here's the scoop: if you ask for the extended scope (in addition to the signature scope) then the returned refresh token will be good for another 30 days every time your app uses the refresh operation.
Example:
Day 1: Human logs into DocuSign using Authorization Code Grant flow via your app. The scope request is signature%20extended Your app receives back from DocuSign an Access Token good for 8 hours and a Refresh Token is good for 30 days. Your app stores the refresh token in non-volatile storage.
Day 23: Human again wants to use your app with DocuSign. Your app uses the Refresh Token it stored to make a refresh token request to DocuSign. (No human interaction is needed.) Your app receives back an Access Token, good for 8 hours, and a new Refresh Token good for 30 days from now (until day 53). Your app stores (replaces) the Refresh Token in non-volatile storage.
Result As long as your app uses/refreshes the refresh token at least once every 30 days, it will forever have a DocuSign refresh token can be used to obtain an Access Token, with no human interaction needed.
Caveat A security issue initiated by the DocuSign customer or by DocuSign itself may cause the Refresh and/or Access token to be no longer valid, and thus require that the user manually authenticate to DocuSign. But this would be an unexpected event and would also affect Access Tokens obtained via the JWT Authentication flow.
Please reserve the JWT flow for the cases where it is required. The JWT flow enables your app to impersonate the user. This is a higher level of access and thus should not be used if there is a good alternative. If the user is present then Authorization Code Grant is the recommended authentication flow.
Re your problem with the JWT flow: Try out our new Java JWT example to see if it helps.

401 Unauthorized when making request to Azure Mobile Service

I have an Azure Mobile service which I am using for authentication. I have a custom auth provider which, once validated, returns the following information:
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(claims, signingKey, audience, issuer, null);
return Ok(new LoginResult()
{
AuthenticationToken = token.RawData,
User = signinedInUser,
StatusCode = System.Net.HttpStatusCode.OK
});
Notice the Timespan is set to null so the token doesn’t expire.
I then make a subsequent request to my AMS which has a controller protected with the Authorize() attribute. However, these are all failing with a 401 Unauthorized response before any of my breakpoints are being hit.
I can see from the Azure logs where this is happening:
2017-10-05T12:18:54 PID[5524] Information Request, Method=POST,
Url=https://mywebsite.azurewebsites.net/api/userinfo/update,
Message='https://mywebsite.azurewebsites.net/api/userinfo/update'
2017-10-05T12:18:54 PID[5524] Information Message='UserInfo',
Operation=DefaultHttpControllerSelector.SelectController
2017-10-05T12:18:54 PID[5524] Information
Message='MyAMS.Controllers.UserInfoController',
Operation=DefaultHttpControllerActivator.Create
2017-10-05T12:18:54 PID[5524] Information
Message='MyAMS.Controllers.UserInfoController',
Operation=HttpControllerDescriptor.CreateController
2017-10-05T12:18:54 PID[5524] Information Message='Selected action
'Update(User cpUser)'',
Operation=ApiControllerActionSelector.SelectAction
2017-10-05T12:18:54 PID[5524] Information Message='Will use same
'JsonMediaTypeFormatter' formatter',
Operation=JsonMediaTypeFormatter.GetPerRequestFormatterInstance
2017-10-05T12:18:54 PID[5524] Information Message='Selected
formatter='JsonMediaTypeFormatter', content-type='application/json;
charset=utf-8'', Operation=DefaultContentNegotiator.Negotiate
2017-10-05T12:18:54 PID[5524] Information
Operation=AuthorizeAttribute.OnAuthorizationAsync, Status=401
(Unauthorized)
2017-10-05T12:18:54 PID[5524] Information
Operation=UserInfoController.ExecuteAsync, Status=401 (Unauthorized)
2017-10-05T12:18:54 PID[5524] Information Response, Status=401
(Unauthorized), Method=POST,
Url=https://mywebsite.azurewebsites.net/api/userinfo/update,
Message='Content-type='application/json; charset=utf-8',
content-length=unknown'
You can see that the Authorize attribute is setting a 401 response:
2017-10-05T12:18:54 PID[5524] Information
Operation=AuthorizeAttribute.OnAuthorizationAsync, Status=401
(Unauthorized)
On the client, I an populating both the User ID and the Auth Token:
this.client = new MobileServiceClient("https://mywebsite.azurewebsites.net");
var user = UserProfileService.GetCurrentSignedInUser();
client.CurrentUser = new MobileServiceUser(user.UserId.ToString())
{
MobileServiceAuthenticationToken = user.AuthToken
};
And stepping through the code I have confirmed that the UserID matches that of the user and also the AuthToken is the same AutToken returned in my login method.
Is there something else I need to set/do to enabled authenticated requests to an Azure Mobile Service?
Thanks
EDIT
I have since disabled all other authentication providers in Azure but this hasn't solved the problem.
I have also debugged the code locally and the issue does not occur running on my localhost, only when deployed to Azure.
According to your code, you are using custom authentication for your azure mobile app. As adrian hall's book about Custom Authentication:
You must turn on Authentication / Authorization in your App Service. Set the Action to take when request is not authenticated to Allow Request (no action) and do not configure any of the supported authentication providers.
Moreover, I would recommend you just use postman or fiddler to simulate the request against your api endpoint, you need to add a new X-ZUMO-AUTH header and set the value to AuthToken to narrow this issue. Also, you could check this issue on your local side, then simulate the request with the token to see whether your code could work on your side or this is the issue on azure side. For your client, you could use client.LoginAsync("custom", JObject.FromObject(user)) for logging without setting the CurrentUser by yourself. For more details, you could follow adrian hall's book to check this issue.
UPDATE:
According to your comments, I tested it on my side. I used the UseAppServiceAuthentication middleware both on my local side and azure side, and read the SigningKey, ValidAudience, ValidIssuer from my web.config under Startup.MobileApp.cs as follows:
//if (string.IsNullOrEmpty(settings.HostName))
{
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Note: I enable the Authentication / Authorization on azure. For my CustomAuthController.cs I called AppServiceLoginHandler.CreateToken with the setting from my web.config as the UseAppServiceAuthentication middleware initialized. Upon the settings, it could work as expected on my side and azure side.
Then I disable the UseAppServiceAuthentication middleware when deployed to azure side. I encountered the 401 as you mentioned, I assumed that the token validation may fail. I rechecked Custom Authentication and found that you need to init SigningKey, ValidAudience, ValidIssuer from the environment variables under your CustomAuthController.cs as follows:
signingKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var website = Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME");
audience = $"https://{website}/";
issuer = $"https://{website}/";
Use jwt.io to decode the token, the iss and aud have changed, but I found it would still return 401. I noticed that we both set lifetime to null when invoke AppServiceLoginHandler.CreateToken. I tried to specific it with a value (e.g. TimeSpan.FromDays(30)), then it works on azure side.
In summary, this issue may due to the lifetime parameter value when invoke AppServiceLoginHandler.CreateToken, and you need to set a specific value instead of null on azure side. Moreover, you could add your issue here for professional explanation.
Additionally, your mobile backend would use AppServiceTokenHandler.cs for handling security token, you could override it and specific the TokenHandler parameter when using the UseAppServiceAuthentication middleware, then you could debug the token validation processing.

Unauthorized, The input authorization token can't serve the request

I am trying to obtain documentdbclient using the resourcetokens. I have a redis cluster with key as user identity and value as the resourcetoken.
I have a service that uses master key to generate resourcetokens for the user and updates them in Redis. I am using the below code to create resource token in my master service
ResourceResponse<Permission> readPermissions = documentClient.readPermission("/dbs/customerdb/users/mobileuser/permissions/readperm", null);
String accessToken=permission.getToken();
DocumentClient documentClient = new DocumentClient(HOST, MASTER_KEY,
ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);
Then i use below code to get resourcetoken and store it in redis
jedis.put("Client_1",readPermissions .getResource().getToken());
Now, at client side when i try to create documentClient using the resourcetoken
DocumentClient manageClient = new DocumentClient(HOST, jedis.get("Client_1"),ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);
I get logs stating unauthorized and following that the error
Unauthorized, The input authorization token can't serve the request
I have created a user called mobileuser on database customerdb and permission with mode PermissionMode.Read on collection customers
I changed my code to be very sure that the tokens are not getting expired but still getting error
java.lang.IllegalStateException: com.microsoft.azure.documentdb.DocumentClientException: The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign: 'get
colls
dxhxakm3caa=
mon, 05 jun 2017 08:56:40 gmt
below id the code that i used to get the token
ResourceResponse<Permission> permissions=documentClient.readPermission("/dbs/customerdd/users/mobileuser/permissions/readperm", null);
System.out.println(permissions.getResource().getResourceLink());
DocumentClient managedClient=new DocumentClient(HOST,permissions.getResource().getToken(), ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);
FeedResponse<Document> response = managedClient.queryDocuments(collection.getResource().getSelfLink(), "SELECT customers.source FROM customers where customers.source='direct-mail'", null);
Iterator<Document> itr = response.getQueryIterator();
while(itr.hasNext()){
Document doc=itr.next();
System.out.println(doc.get("source"));
}
Any pointer will be of a great help
Unauthorized, The input authorization token can't serve the request
As far as I know, the default valid timespan of the resource token is one hour. And if the resource token expires, subsequent requests receive a 401 unauthorized exception, please make sure if the resource token is expired when you retrieve it from Redis cache.
Update:
If i use the overloaded constructor of DocumentClient and pass the PermissionFeed then it works
DocumentClient class has two constructors as below, and when you use new DocumentClient(HOST, jedis.get("Client_1"),ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);, it seems that it recognizes jedis.get("Client_1") that you passed as a string and use the second constructor to initialize a new instance, which would be the cause of the issue.

Azure returns valid access token with an invalid client secret

Azure access token is returned even with an invalid secret when run in a multithreaded environment.
I've got an integration test that checks to see that an invalid client secret won't pass when getting an Azure access token.
When run in isolation the test passes every time, meaning that an invalid client secret does not return an Azure access token.
However, when run with other integration tests (on multiple threads) this function returns an access token even with an obviously invalid client secret.
I don't see any legitimate reason this would be a cached token for the client id even when specifying a totally invalid client secret.
Note, this behavior does not happen when the client id is invalid.
Is there an explanation for this behavior?
private async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
var context = new AuthenticationContext(authority, TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, clientCredential);
Debug.WriteLine("----------------------------------");
Debug.WriteLine(clientId);
Debug.WriteLine(clientSecret);
Debug.WriteLine(result.AccessToken);
return result.AccessToken;
}
The debug output is
Debug Trace:
----------------------------------
<...client id...>
invalid secret
<...valid token...>
This is because your cache still has a valid access token in the cache. ADAL checks the cache first and returns the access token if still valid (not expired). Token cache pivots on client_id as one of the dimensions of the key, so invalid client_id fails as expected. To force the library to use the secret and make a network call, you must delete the token from the cache
Clear client credential token like below. This AuthenticationContext cache the credentials. clear them before checking another pair of keys.
var authenticationContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenantId}/v2.0");
if (authenticationContext?.TokenCache.Count > 0)
{
authenticationContext.TokenCache.Clear();
}

Resources