Skip to content

Proposal to add option for Method Level Authorization header attribute#897

Draft
Roflincopter wants to merge 1 commit intochristianhelle:mainfrom
Roflincopter:add-option-to-generate-method-level-authorization-header-attribute
Draft

Proposal to add option for Method Level Authorization header attribute#897
Roflincopter wants to merge 1 commit intochristianhelle:mainfrom
Roflincopter:add-option-to-generate-method-level-authorization-header-attribute

Conversation

@Roflincopter
Copy link

@Roflincopter Roflincopter commented Feb 13, 2026


name: Pull request
title: ''
labels: enhancement
assignees: christianhelle


Description:

A previous PR introduced an optional parameter for authentication headers. As far as I could tell this functionality was never exposed to the commandline tool. and never added support for Bearer tokens.

I want to use this opportunity to both add this to the cli tool and either break compat or write a backwards compatible conversion. To move away from the boolean type in favor of an enum. And add to this enum a way to generate the authenticatoin in the [Headers] attribute for a method. This alleviates the burden of adding the authentication header contents as a parameter on each request and in case of the bearer token authentication it can be configured on refit client creation like this

collection.AddRefitClient<IGamenightApi>(new RefitSettings
            {
                AuthorizationHeaderValueGetter = state.GetBearerToken,
            })
            .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:8080"));

Issues

  • It still lacks tests, something I intend to add if this change is desired.
  • Determine if acceptable if exposing a previous bool Settings as an enum.

Example OpenAPI Specifications:

openapi: 3.0.0
info:
  title: Example
  version: '1.0'
servers:
  - url: 'http://localhost:8080'
security:
  - JWT-Auth: [ ]
paths:
  /token:
    post:
      summary: 'Login a user.'
      operationId: post-token
      responses:
        '200':
          $ref: '#/components/responses/TokenResponse'
        '401':
          $ref: '#/components/responses/FailureResponse'
      requestBody:
        $ref: '#/components/requestBodies/LoginRequest'
      description: Submit your credentials to get a JWT-token to use with the rest of the api.
      parameters: []
      security: []
  /refresh_token:
    post:
      summary: 'Refresh a user token'
      operationId: post-refresh-token
      responses:
        '200':
          $ref: '#/components/responses/TokenResponse'
        '401':
          $ref: '#/components/responses/FailureResponse'
      description: Refresh your JWT-token without logging in again.
      parameters: []
    
components:
  schemas:
    Failure:
      title: Failure
      type: object
      properties:
        message:
          type: string
      description: 'Failure Reason'
    Token:
      title: Token
      type: object
      properties:
        jwt_token:
          type: string
    Login:
      title: Login
      type: object
      properties:
        username:
          type: string
        password:
          type: string
      required:
        - username
        - password
  requestBodies:
    LoginRequest:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Login'
  responses:
    TokenResponse:
      description: Example response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Token'
    FailureResponse:
      description: Example response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Failure'
  securitySchemes:
    JWT-Auth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: ''

Example output

with dotnet run --framework net10.0 -- --generate-authentication-header Method ~/projects/refitter/test/example.yaml aka new behaviour

// <auto-generated>
//     This code was generated by Refitter.
// </auto-generated>


using Refit;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

#nullable enable annotations

namespace GeneratedCode
{
    /// <summary>Example</summary>
    [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
    public partial interface IExample
    {
        /// <summary>Login a user.</summary>
        /// <remarks>Submit your credentials to get a JWT-token to use with the rest of the api.</remarks>
        /// <param name="body">body parameter</param>
        /// <returns>Example response</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>401</term>
        /// <description>Example response</description>
        /// </item>
        /// </list>
        /// </exception>
        [Post("/token")]
        Task<Token> PostToken([Body] Login body);

        /// <summary>Refresh a user token</summary>
        /// <remarks>Refresh your JWT-token without logging in again.</remarks>
        /// <returns>Example response</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>401</term>
        /// <description>Example response</description>
        /// </item>
        /// </list>
        /// </exception>
        [Headers("Authorization: Bearer")]
        [Post("/refresh_token")]
        Task<Token> PostRefreshToken();


    }

}

//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------

#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null"
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
#pragma warning disable 8600 // Disable "CS8600 Converting null literal or possible null value to non-nullable type"
#pragma warning disable 8602 // Disable "CS8602 Dereference of a possibly null reference"
#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"
#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter"
#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type"
#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)."

namespace GeneratedCode
{
    using System = global::System;

    

    /// <summary>
    /// Failure Reason
    /// </summary>
    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class Failure
    {

        [JsonPropertyName("message")]
        public string Message { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class Token
    {

        [JsonPropertyName("jwt_token")]
        public string JwtToken { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class Login
    {

        [JsonPropertyName("username")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Username { get; set; }

        [JsonPropertyName("password")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Password { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }


}

#pragma warning restore  108
#pragma warning restore  114
#pragma warning restore  472
#pragma warning restore  612
#pragma warning restore  649
#pragma warning restore 1573
#pragma warning restore 1591
#pragma warning restore 8073
#pragma warning restore 3016
#pragma warning restore 8600
#pragma warning restore 8602
#pragma warning restore 8603
#pragma warning restore 8604
#pragma warning restore 8625
#pragma warning restore 8765

Example output with dotnet run --framework net10.0 -- --generate-authentication-header Parameter ~/projects/refitter/test/example.yaml aka old behaviour

// <auto-generated>
//     This code was generated by Refitter.
// </auto-generated>


using Refit;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

#nullable enable annotations

namespace GeneratedCode
{
    /// <summary>Example</summary>
    [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.0.0.0")]
    public partial interface IExample
    {
        /// <summary>Login a user.</summary>
        /// <remarks>Submit your credentials to get a JWT-token to use with the rest of the api.</remarks>
        /// <param name="body">body parameter</param>
        /// <returns>Example response</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>401</term>
        /// <description>Example response</description>
        /// </item>
        /// </list>
        /// </exception>
        [Post("/token")]
        Task<Token> PostToken([Body] Login body);

        /// <summary>Refresh a user token</summary>
        /// <remarks>Refresh your JWT-token without logging in again.</remarks>
        /// <returns>Example response</returns>
        /// <exception cref="ApiException">
        /// Thrown when the request returns a non-success status code:
        /// <list type="table">
        /// <listheader>
        /// <term>Status</term>
        /// <description>Description</description>
        /// </listheader>
        /// <item>
        /// <term>401</term>
        /// <description>Example response</description>
        /// </item>
        /// </list>
        /// </exception>
        [Post("/refresh_token")]
        Task<Token> PostRefreshToken([Header("Authorization: Bearer" )] string bearerToken);


    }

}

//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------

#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null"
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
#pragma warning disable 8600 // Disable "CS8600 Converting null literal or possible null value to non-nullable type"
#pragma warning disable 8602 // Disable "CS8602 Dereference of a possibly null reference"
#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"
#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter"
#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type"
#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)."

namespace GeneratedCode
{
    using System = global::System;

    

    /// <summary>
    /// Failure Reason
    /// </summary>
    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class Failure
    {

        [JsonPropertyName("message")]
        public string Message { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class Token
    {

        [JsonPropertyName("jwt_token")]
        public string JwtToken { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }

    [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.6.3.0 (NJsonSchema v11.5.2.0 (Newtonsoft.Json v13.0.0.0))")]
    public partial class Login
    {

        [JsonPropertyName("username")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Username { get; set; }

        [JsonPropertyName("password")]
        [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
        public string Password { get; set; }

        private IDictionary<string, object> _additionalProperties;

        [JsonExtensionData]
        public IDictionary<string, object> AdditionalProperties
        {
            get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
            set { _additionalProperties = value; }
        }

    }


}

#pragma warning restore  108
#pragma warning restore  114
#pragma warning restore  472
#pragma warning restore  612
#pragma warning restore  649
#pragma warning restore 1573
#pragma warning restore 1591
#pragma warning restore 8073
#pragma warning restore 3016
#pragma warning restore 8600
#pragma warning restore 8602
#pragma warning restore 8603
#pragma warning restore 8604
#pragma warning restore 8625
#pragma warning restore 8765

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Roflincopter Roflincopter force-pushed the add-option-to-generate-method-level-authorization-header-attribute branch from 59881ac to 7dbf6c0 Compare February 13, 2026 19:15
@sonarqubecloud
Copy link

@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 16.66667% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.67%. Comparing base (5197bca) to head (7dbf6c0).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
src/Refitter.Core/RefitInterfaceGenerator.cs 12.50% 7 Missing ⚠️
src/Refitter.Core/ParameterExtractor.cs 33.33% 1 Missing and 1 partial ⚠️
src/Refitter/Settings.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #897      +/-   ##
==========================================
- Coverage   92.95%   92.67%   -0.28%     
==========================================
  Files          23       23              
  Lines        2031     1407     -624     
==========================================
- Hits         1888     1304     -584     
- Misses         48       56       +8     
+ Partials       95       47      -48     
Flag Coverage Δ
unittests 92.67% <16.66%> (-0.28%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@christianhelle christianhelle self-assigned this Feb 13, 2026
@christianhelle christianhelle added the enhancement New feature, bug fix, or request label Feb 13, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request enhances authentication header generation by replacing a boolean setting with an enum-based approach and adding support for bearer token authentication. The changes enable generating authentication headers either as method-level attributes (using Refit's AuthorizationHeaderValueGetter) or as method parameters, addressing a previously unexposed feature and extending it to support modern bearer token authentication patterns.

Changes:

  • Introduces AuthenticationHeaderStyle enum with three modes: None, Parameter, and Method
  • Exposes authentication header generation to the CLI tool via --generate-authentication-header option
  • Adds bearer token support for both Parameter and Method styles
  • Updates all existing tests to use the new enum-based API

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/Refitter.Core/Settings/AuthenticationHeaderStyle.cs New enum defining three authentication header generation modes
src/Refitter.Core/Settings/RefitGeneratorSettings.cs Replaces boolean property with enum-typed property
src/Refitter/Settings.cs Adds CLI option for authentication header generation
src/Refitter/GenerateCommand.cs Maps CLI setting to core generator setting
src/Refitter.Core/RefitInterfaceGenerator.cs Implements Method-style header generation
src/Refitter.Core/ParameterExtractor.cs Adds bearer token support to Parameter-style generation
src/Refitter.Tests/SwaggerPetstoreTests.cs Updates tests to use new enum API
src/Refitter.Tests/SwaggerPetstoreApizrTests.cs Updates Apizr tests to use new enum API

Comment on lines +292 to +298
if (securityScheme is { Type: OpenApiSecuritySchemeType.ApiKey, In: OpenApiSecurityApiKeyLocation.Header }
&& !operationModel.Parameters.Any(p => p is { Kind: OpenApiParameterKind.Header, IsHeader: true } && p.Name == securityScheme.Name))
{
headers.Add($"\"{securityScheme.Name}\"");
}
else if (securityScheme is { Type: OpenApiSecuritySchemeType.Http, Scheme: "bearer" })
{
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for handling ApiKey headers in Method style appears problematic. At line 295, the code adds only the header name without a value (e.g., "auth_key"), which would generate [Headers("auth_key")]. However, Refit's Headers attribute expects the format "HeaderName: value" for static headers. Since ApiKey headers require dynamic values (the actual API key), they cannot be meaningfully represented as static method-level headers. Consider either removing this branch for ApiKey headers in Method style, or documenting that Method style only supports HTTP Bearer authentication and not ApiKey authentication.

Suggested change
if (securityScheme is { Type: OpenApiSecuritySchemeType.ApiKey, In: OpenApiSecurityApiKeyLocation.Header }
&& !operationModel.Parameters.Any(p => p is { Kind: OpenApiParameterKind.Header, IsHeader: true } && p.Name == securityScheme.Name))
{
headers.Add($"\"{securityScheme.Name}\"");
}
else if (securityScheme is { Type: OpenApiSecuritySchemeType.Http, Scheme: "bearer" })
{
if (securityScheme is { Type: OpenApiSecuritySchemeType.Http, Scheme: "bearer" })
{

Copilot uses AI. Check for mistakes.
Comment on lines 280 to 331
[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3WithUnsafeAuthenticationHeaders, "SwaggerPetstoreWithUnsafeAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3WithUnsafeAuthenticationHeaders, "SwaggerPetstoreWithUnsafeAuthenticationHeaders.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2WithUnsafeAuthenticationHeaders, "SwaggerPetstoreWithUnsafeAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2WithUnsafeAuthenticationHeaders, "SwaggerPetstoreWithUnsafeAuthenticationHeaders.yaml")]
public async Task Can_Generate_Code_With_Unsafe_AuthenticationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new RefitGeneratorSettings();
settings.GenerateAuthenticationHeader = true;
settings.AuthenticationHeaderStyle = AuthenticationHeaderStyle.Parameter;
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().Contain("[Header(\"auth.key\")] string auth_key");
}

[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
public async Task Can_Generate_Code_With_AuthenticationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new RefitGeneratorSettings();
settings.GenerateAuthenticationHeader = true;
settings.AuthenticationHeaderStyle = AuthenticationHeaderStyle.Parameter;
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().Contain("[Header(\"auth_key\")] string auth_key");
}

[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
public async Task Can_Generate_Code_With_AuthenticationHeaders_Without_OperationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new RefitGeneratorSettings();
settings.GenerateOperationHeaders = false;
settings.GenerateAuthenticationHeader = true;
settings.AuthenticationHeaderStyle = AuthenticationHeaderStyle.Parameter;
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().Contain("[Header(\"auth_key\")] string auth_key");
}

[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3, "SwaggerPetstore.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2, "SwaggerPetstore.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2, "SwaggerPetstore.yaml")]
public async Task Can_Generate_Code_Without_AuthenticationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new RefitGeneratorSettings();
settings.GenerateAuthenticationHeader = false;
settings.AuthenticationHeaderStyle = AuthenticationHeaderStyle.None;
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().NotContain("[Header(\"auth_key\")] string? auth_key");
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no tests for the new AuthenticationHeaderStyle.Method behavior. The PR introduces a new way to generate authentication headers, but all existing tests only cover AuthenticationHeaderStyle.Parameter and AuthenticationHeaderStyle.None. Add tests that verify Method style generates the correct [Headers("Authorization: Bearer")] attribute for bearer token authentication, and ensure the generated code compiles and behaves correctly. Consider testing with both the existing Swagger Petstore specs and a new spec that includes bearer token security schemes.

Copilot uses AI. Check for mistakes.
Comment on lines 280 to 323
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2WithUnsafeAuthenticationHeaders, "SwaggerPetstoreWithUnsafeAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2WithUnsafeAuthenticationHeaders, "SwaggerPetstoreWithUnsafeAuthenticationHeaders.yaml")]
public async Task Can_Generate_Code_With_Unsafe_AuthenticationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new ApizrGeneratorSettings { GenerateAuthenticationHeader = true };
var settings = new ApizrGeneratorSettings { AuthenticationHeaderStyle = AuthenticationHeaderStyle.Parameter };
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().Contain("[Header(\"auth.key\")] string auth_key");
}

[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
public async Task Can_Generate_Code_With_AuthenticationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new ApizrGeneratorSettings { GenerateAuthenticationHeader = true };
var settings = new ApizrGeneratorSettings { AuthenticationHeaderStyle = AuthenticationHeaderStyle.Parameter };
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().Contain("[Header(\"auth_key\")] string auth_key");
}

[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2WithAuthenticationHeaders, "SwaggerPetstoreWithAuthenticationHeaders.yaml")]
public async Task Can_Generate_Code_With_AuthenticationHeaders_Without_OperationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new ApizrGeneratorSettings { GenerateOperationHeaders = false, GenerateAuthenticationHeader = true };
var settings = new ApizrGeneratorSettings { GenerateOperationHeaders = false, AuthenticationHeaderStyle = AuthenticationHeaderStyle.Parameter };
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().Contain("[Header(\"auth_key\")] string auth_key");
}

[Test]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV3, "SwaggerPetstore.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV3, "SwaggerPetstore.yaml")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreJsonV2, "SwaggerPetstore.json")]
[Arguments(SampleOpenSpecifications.SwaggerPetstoreYamlV2, "SwaggerPetstore.yaml")]
public async Task Can_Generate_Code_Without_AuthenticationHeaders(SampleOpenSpecifications version, string filename)
{
var settings = new ApizrGeneratorSettings { GenerateAuthenticationHeader = false };
var settings = new ApizrGeneratorSettings { AuthenticationHeaderStyle = AuthenticationHeaderStyle.None };
var generatedCode = await GenerateCode(version, filename, settings);
generatedCode.Should().NotContain("[Header(\"auth_key\")] string? auth_key");
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the tests in SwaggerPetstoreTests.cs, there are no tests for the new AuthenticationHeaderStyle.Method behavior. Add tests to verify that the Method style generates the correct [Headers("Authorization: Bearer")] attribute when using Apizr settings.

Copilot uses AI. Check for mistakes.
[DefaultValue(null)]
public string? CustomTemplateDirectory { get; set; } = null;

[Description("Generate Authorization header method 'Method' or 'Parameter'. Default value is None")]
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CLI option --generate-authentication-header should be documented in the README.md file. Add a description explaining the three modes (None, Parameter, Method), when to use each, and provide examples showing the generated output for both Parameter and Method styles. The Parameter style should explain that it adds method parameters for authentication, while Method style should explain that it uses Refit's AuthorizationHeaderValueGetter delegate for bearer tokens.

Suggested change
[Description("Generate Authorization header method 'Method' or 'Parameter'. Default value is None")]
[Description("Controls generation of Authorization header support. Options: None (no authentication code is generated), Parameter (adds method parameters such as string accessToken that are used to build the Authorization: Bearer header for each call), and Method (generates a Refit AuthorizationHeaderValueGetter delegate that returns a bearer token used for the Authorization header). Example Parameter style output: Task<User> GetUserAsync(string id, string accessToken, CancellationToken cancellationToken = default). Example Method style output: Task<User> GetUserAsync(string id, CancellationToken cancellationToken = default) with a generated AuthorizationHeaderValueGetter that supplies the bearer token. Default value is None.")]

Copilot uses AI. Check for mistakes.
/// The Authentication header style to use.
/// </summary>
[Description("The Authentication header style to use")]
public enum AuthenticationHeaderStyle {
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enum should not have a space after the opening brace. C# convention is to place the opening brace on the same line without a trailing space. This should be public enum AuthenticationHeaderStyle { changed to public enum AuthenticationHeaderStyle.

Suggested change
public enum AuthenticationHeaderStyle {
public enum AuthenticationHeaderStyle
{

Copilot uses AI. Check for mistakes.
@@ -378,7 +378,7 @@ payloads with (yet) unknown types are offered by newer versions of an API
/// Gets or sets a value indicating whether to generate Security Schema Authentication headers.
/// </summary>
[Description("Generate Security Schema Authentication headers")]
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AuthenticationHeaderStyle property should have a JsonConverter attribute to ensure consistent serialization when saving to .refitter files. Other enum properties in this file like CollectionFormat (line 388) use [JsonConverter(typeof(JsonStringEnumConverter))]. Add this attribute above the property declaration to maintain consistency.

Suggested change
[Description("Generate Security Schema Authentication headers")]
[Description("Generate Security Schema Authentication headers")]
[JsonConverter(typeof(JsonStringEnumConverter))]

Copilot uses AI. Check for mistakes.
Comment on lines +302 to +303

}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is trailing whitespace after the closing brace on this line. Remove the trailing space for consistency with the codebase style.

Copilot uses AI. Check for mistakes.
@christianhelle
Copy link
Owner

@Roflincopter thanks for taking the time to investigate and implement this

I remember we had this discussion in the past regarding authorization headers, and if my memory serves me right, then I think back then we discussed that the best approach to handle authorization was through a HttpClient delegating handler hooked up to the HttpClientFactory configured for the HttpClient used by Refit.

That's at least how I always did authorization. The actual authentication is handled by some secure token service which in the end gives me an OAuth access token and refresh token, then I'll have some tooling that handles adding the access token to the authorization header as a bearer token. The same delegating handler (or chain of delegating handlers) would handle checking the validity of said access token, and would also be responsible of renewing the access token using the refresh token

The example you you're suggesting seems like the Refit client will be used directly for making authentication or login calls then use the response of that as the authorization header. It's not something I would ever use but since you took the time to build this I guess its something you have a specific use case for.

I don't mind getting this in but I'll to test various scenarios for this first. It's important that this doesn't break the default behavior as Refitter has gained popularity over the years and it's user base has grown massively

I'll review this thoroughly over the weekend

@Roflincopter
Copy link
Author

Yeah we had. My use case admittedly is very small, It's just that Refit has support for this style of Bearer token authorization, Admittedly the AI points out that it would only work for the Authorization: Bearer.

The strategy of delegating handlers is a valid one, but I'm concerned with how I can discern what functions need the authorization header and which do not. The openApi spec knows. but when the interface no longer knows I need to re-encode some of the openapi specification in my library code. So i'm fine if you reject this as being too niche.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature, bug fix, or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants