Base solution for your next web application
Open Closed

Update issue from 8.1 to 12.4: AuthConfigurer.cs with token validation issue #11943


User avatar
0
ips-ad created

Hi, We're currently updating our project from Zero 8.1 (Angular, .net) to 12.4.2. Our project has an integration for Onlyoffice, a web based solution to edit office documents. Opening the editor still works fine, i.e. we provide it with basically a FileDto and a set of config parameters, then it downloads the document from our Zero backend and displays it for editing.

The issue now is when Onlyoffice needs to write an edited document back to our Zero backend. It authenticates via JWT with a pre-shared secret, therefor we extended AuthConfigurer.cs with the last if-block (configuration["OnlyOffice:IsEnabled"]):

    public static class AuthConfigurer
    {
        public static void Configure(IServiceCollection services, IConfiguration configuration)
        {
            var authenticationBuilder = services.AddAuthentication();
            
            if (bool.Parse(configuration["Authentication:JwtBearer:IsEnabled"]))
            {
                authenticationBuilder.AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        // The signing key must match!
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration["Authentication:JwtBearer:SecurityKey"])),

                        // Validate the JWT Issuer (iss) claim
                        ValidateIssuer = true,
                        ValidIssuer = configuration["Authentication:JwtBearer:Issuer"],

                        // Validate the JWT Audience (aud) claim
                        ValidateAudience = true,
                        ValidAudience = configuration["Authentication:JwtBearer:Audience"],

                        // Validate the token expiry
                        ValidateLifetime = true,

                        // If you want to allow a certain amount of clock drift, set that here
                        ClockSkew = TimeSpan.Zero
                    };

                    options.SecurityTokenValidators.Clear();
                    options.SecurityTokenValidators.Add(new IPSwebJwtSecurityTokenHandler());

                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = QueryStringTokenResolver
                    };
                });
            }

            if (bool.Parse(configuration["IdentityServer:IsEnabled"]))
            {
                IdentityModelEventSource.ShowPII = true;
                authenticationBuilder.AddIdentityServerAuthentication("IdentityBearer", options =>
                {
                    options.Authority = configuration["IdentityServer:Authority"];
                    options.ApiName = configuration["IdentityServer:ApiName"];
                    options.ApiSecret = configuration["IdentityServer:ApiSecret"];
                    options.RequireHttpsMetadata = false;
                });
            }

            if (bool.Parse(configuration["OnlyOffice:IsEnabled"]))
            {
                var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(configuration["OnlyOffice:CallBackSecret"]));
                services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer("OnlyOfficeBearerToken",
                    options =>
                    {
                        options.TokenValidationParameters = new TokenValidationParameters()
                        {
                            ValidateAudience = false,
                            ValidateActor = false,
                            ValidateIssuer = false,
                            ValidateIssuerSigningKey = true,
                            ValidateLifetime = true,
                            IssuerSigningKey = securityKey
                        };
                        options.RequireHttpsMetadata = false;
                        options.SaveToken = true;
                    });
            }
        }

Then, a controller in the Zero Backend takes care of the calls returning from Onlyoffice like this:

        [DisableAuditing]
        [Authorize(AuthenticationSchemes = "OnlyOfficeBearerToken")]
        [HttpPost]
        [RequestSizeLimit(100_000_000)]
        public async Task<ActionResult> OnlyOfficeCallbackHandler()
        {
            Claim payloadclaim = this.HttpContext.User.FindFirst(c => c.Type == "payload");
            string body = payloadclaim?.Value;
            //proceed body etc...

This worked well for many years, but now is broken since the update to 12.4.2, Onlyoffice giving the following errors:

Error: Error response: statusCode:302; headers:{"server":"nginx/1.21.1","date":"Mon, 01 Apr 2024 08:43:22 GMT","content-length":"0","connection":"keep-alive","location":"/Error?statusCode=401","www-authenticate":"Bearer error=\"invalid_token\", error_description=\"The signature key was not found; The signature key was not found\""

...and Zero giving the following errors:

Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10503: Signature validation failed. Token does not have a kid. Keys tried: '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Number of keys in TokenValidationParameters: '1'. 
Number of keys in Configuration: '0'. 
Exceptions caught:
 '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
token: '[PII of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. See https://aka.ms/IDX10503 for details.
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignatureAndIssuerSecurityKey(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown)
--- End of stack trace from previous location ---

We tried many things, like adding a kid, but it looks just like it's not possible anymore to extend AuthConfigurer.cs like before. Is there an easy way to fix it, or do we have to switch the approach?

This is how Onlyoffice works: https://api.onlyoffice.com/editors/save

Thanks for your help!


7 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ips-ad

    Could you share which endpoint you are using to get the token ?

  • User Avatar
    0
    ips-ad created

    Hi @ismcagdas

    When calling/opening Onlyoffice, we have to build the token like this from the Angular frontend:

    let oHeader = { alg: 'HS256', typ: 'JWT' };
    let sHeader = JSON.stringify(oHeader);
    let sPayload = JSON.stringify(config);
    let sJWT = KJUR.jws.JWS.sign('HS256', sHeader, sPayload, this.officeCredentials);
    

    This token was and is still fine, it's accepted by Onlyoffice with the same officeCredentials configured. Onlyoffice is then downloading the document from Zero backend via /File/DownloadTempFile/....

    Onlyoffice then starts to make calls back to the Zero backend, where it builds the token in the same way (as described here https://api.onlyoffice.com/editors/security). We have a custom controller/endpoint class which handles these callbacks for status handling and saving the document back to our DB on closing. This is the part that worked with 8.1, but doesn't with 12.4.2, as described in my original post:

    public class DocumentController : XXXControllerBase
    [...]
            [DisableAuditing]
            [Authorize(AuthenticationSchemes = "OnlyOfficeBearerToken")]
            [HttpPost]
            [RequestSizeLimit(100_000_000)]
            public async Task OnlyOfficeCallbackHandler()
            {
                Claim payloadclaim = this.HttpContext.User.FindFirst(c => c.Type == "payload");
                string body = payloadclaim?.Value;
                //proceed body etc...
    
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Maybe I couldn't understand the use case correctly but, if you want to call an endpoint on AspNet Zero's APIs, you should get the token from its Token endpoint https://docs.aspnetzero.com/en/aspnet-core-angular/latest/Features-Angular-Token-Based-Authentication

  • User Avatar
    0
    ips-ad created

    Thanks for your reply. I had a look at this documentation in the past already, but unfortunately that won't work in this case, at least as far as I understand.

    The essential part is that the Onlyoffice server must be able to make calls to our Zero endpoint. Onlyoffice provides a token with that, signed with a security key that both sides share. We have no chance to change anything on the Onlyoffice side, except for the security key of course. We don't have user context with these calls, but that's not an issue because we know the record id of the concerned document in our database. So basically we need a Zero endpoint accepting the calls from Onlyoffice and verifying the token against a shared security key as they are.

    It worked fine in Zero 8.1 with adding this to AuthConfigurer.cs (and the related settings in appconfig.json of course):

    [...]
            public static void Configure(IServiceCollection services, IConfiguration configuration)
            {
    [...]
                if (bool.Parse(configuration["OnlyOffice:IsEnabled"]))
                {
                    var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(configuration["OnlyOffice:CallBackSecret"]));
                    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer("OnlyOfficeBearerToken",
                        options =>
                        {
                            options.TokenValidationParameters = new TokenValidationParameters()
                            {
                                ValidateAudience = false,
                                ValidateActor = false,
                                ValidateIssuer = false,
                                ValidateIssuerSigningKey = true,
                                ValidateLifetime = true,
                                IssuerSigningKey = securityKey //the security key shared with Onlyoffice
                            };
                            options.RequireHttpsMetadata = false;
                            options.SaveToken = true;
                        });
                }
    [...]
    

    ...and creating a controller for the Zero API endpoint like this:

    public class DocumentController : XXXControllerBase
    [...]
            [DisableAuditing]
            [Authorize(AuthenticationSchemes = "OnlyOfficeBearerToken")]
            [HttpPost]
            [RequestSizeLimit(100_000_000)]
            public async Task OnlyOfficeCallbackHandler()
            {
                Claim payloadclaim = this.HttpContext.User.FindFirst(c => c.Type == "payload");
                string body = payloadclaim?.Value;
                //proceed body etc...
    [...]
    

    With Zero 12.4.2, it doesn't accept the token from Onlyoffice anymore, as mentioned initially:

    Failed to validate the token.
    Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10503: Signature validation failed. Token does not have a kid. Keys tried: '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Number of keys in TokenValidationParameters: '1'. 
    Number of keys in Configuration: '0'. 
    Exceptions caught:
     '[PII of type 'System.Text.StringBuilder' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
    token: '[PII of type 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. See https://aka.ms/IDX10503 for details.
       at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
       at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignatureAndIssuerSecurityKey(String token, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
       at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateJWS(String token, TokenValidationParameters validationParameters, BaseConfiguration currentConfiguration, SecurityToken& signatureValidatedToken, ExceptionDispatchInfo& exceptionThrown)
    --- End of stack trace from previous location ---
    

    I hope that I could explain it in such a way that it makes sense to you :-)

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ips-ad

    Did you update your existing solution to 12.4.2 or did you try this on a brand new project ? If it was an updated project, could you download a new project from https://aspnetzero.com/download and try it on the new project ?

  • User Avatar
    0
    ips-ad created

    It's an updated solution but I think we have overwritten all those "basic" parts with the files of the brand new 12.4.2 project. I'll check when there is some time left for this!

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ips-ad

    If you can't resolve this issue, is it possible for you to share your project via email with [email protected] and also share the steps to reproduce the issue ?