This website may use cookies. More info. That's Fine x
Welcome Login

Web api: Api key based authentication for Create (Post request)


Examples:

Eg: validate API key, generate ID, create record and return created resource:

Create api key auth class in a folder called 'Filters' (can be any name really).

// api key auth attribute filter:
// ApiKeyAuthAttribute.cs
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Net;
using System.Net.Http;

namespace WebApi_ApiKey1.Filters
{
    public class ApiKeyAuthAttribute : AuthorizeAttribute
    {
        private const string API_KEY_HEADER = "X-API-KEY";
        private const string VALID_API_KEY = "my-secret-key-123"; // move to config in real apps


        protected override bool IsAuthorized(HttpActionContext actionContext)
        {
            // check header exists
            if (!actionContext.Request.Headers.Contains(API_KEY_HEADER))
            {
                return false;
            }

            // retrieve api key value from header
            var apiKey = actionContext.Request.Headers
                .GetValues(API_KEY_HEADER)
                .FirstOrDefault();

            // validate api
            return apiKey == VALID_API_KEY;
        }


        protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
        {
            actionContext.Response = actionContext.Request
                .CreateResponse(HttpStatusCode.Unauthorized, "Invalid or missing API key");
        }
    }
}



// webapi controller and model:
// PostController.cs
using WebApi_ApiKey1.Filters;    // folder

namespace WebApi_ApiKey1.Controllers
{
    [ApiKeyAuth]
    [RoutePrefix("api/records")]
    public class PostController : ApiController
    {
        private static List<Record> Records = new List<Record>
        {
            new Record { Id = 1, Value = "First" },
            new Record { Id = 2, Value = "Second" }
        };


        // POST api/records
        [HttpPost]
        [Route("")]
        public IHttpActionResult Create([FromBody] RecordCreateDto dto)
        {
            if (dto == null || string.IsNullOrWhiteSpace(dto.Value))
            {
                return BadRequest("Value is required.");
            }

            // example for small list (in-memory list)
            var newId = Records.Any() ? Records.Max(r => r.Id) + 1 : 1;    // Any() returns true if list has one or more items, else false.
            var record = new Record
            {
               Id = newId,
               Value = dto.Value
            };
            Records.Add(record);

            // ef:
            //var record = new Record { Value = dto.Value };
            //dbContext.Records.Add(record);
            //dbContext.SaveChanges(); // ID auto-generated


            // in ASP.NET Web API, the Created() method is a helper for HTTP 201 Created responses.
            // syntax: Created(string location, object content)
            // location : URL of the newly created resource
            // content : the resource itself (returned in the body)
            // outputs:
            // HTTP / 1.1 201 Created
            // Location: api/records/3
            // Content-Type: application/json
            return Created($"api/records/{record.Id}", record);
        }


        // GET api/records
        [HttpGet]
        [Route("")]
        public IHttpActionResult GetAll()
        {
            return Ok(Records);
        }
    }


    public class RecordCreateDto
    {
        public string Value { get; set; }   // No Id: client cannot control identity
    }
}

 

Test with curl (CREATE):

curl -X POST http://localhost:53923/api/records ^
  -H "Content-Type: application/json" ^
  -H "X-API-KEY: my-secret-key-123" ^
  -d "{\"Value\":\"Created from POST\"}"


^   : caret at the end of a line is a line-continuation character in windows shells (use '\' for linux)  
-X  : specify http request method 'POST'
-H  : add custom headers
-d  : send data in request body 

 

Verify the insert with curl get request:

curl http://localhost:53923/api/records -H "X-API-KEY: my-secret-key-123"

 

Note: if you do not specify an HTTP method in curl, it defaults to GET.

 

Outputs:

webapi-apikey9

 

 

Eg2: test with a client app:

// controller:
// ClientController.cs
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Text;

namespace WebapiClient1.Controllers
{
    public class ClientController : Controller
    {
        private const string ApiBaseUrl = "http://localhost:53923/";
        private const string ApiKey = "my-secret-key-123";


        // GET: /RecordsClient/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: /RecordsClient/Create
        [HttpPost]
        public async Task<ActionResult> Create(string value)
        {
            var dto = new RecordCreateDto
            {
                Value = value
            };

            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(ApiBaseUrl);

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("X-API-KEY", ApiKey);
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                var json = JsonConvert.SerializeObject(dto);
                var content = new StringContent(json, Encoding.UTF8, "application/json");

                var response = await client.PostAsync("api/records", content);

                if (!response.IsSuccessStatusCode)
                {
                    return Content($"API error: {response.StatusCode}");
                }

                var resultJson = await response.Content.ReadAsStringAsync();

                return Content(resultJson, "application/json");
            }
        }

    }

    public class RecordCreateDto
    {
        public string Value { get; set; }
    }
}


// view:
@{
    Layout = null;
    ViewBag.Title = "Create Record";
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Create</title>
</head>
<body>
    <h2>Create Record</h2>

    <form method="post">
        <label>Value:</label><br />
        <input type="text" name="value" required /><br /><br />

        <button type="submit">Create</button>
    </form>
</body>
</html>

 

Outputs:

webapi-apikey10

 

 

Eg3: full example:

Post 2 types of values: leadid and lead status.

// api key auth attribute:
// ApiKeyAuthorizeAttribute.cs
namespace WebApi.LeadQService.Filters
{
    public class ApiKeyAuthorizeAttribute : AuthorizeAttribute
    {
        private const string HeaderName = "BUYER-API-KEY";

        protected override bool IsAuthorized(HttpActionContext actionContext)
        {
            if (!actionContext.Request.Headers.Contains(HeaderName))
            {
                return false;
            }

            var apiKey = actionContext.Request.Headers.GetValues(HeaderName).FirstOrDefault();

            MerchantDal merchantDal = new MerchantDal();
            List<Merchant> merchants = merchantDal.GetMerchants(false);

            Merchant merchant = merchants.FirstOrDefault(m => m.LeadQApiKey == apiKey 
                && m.LeadQIsEnabled == true);

            // save merchant id
            actionContext.Request.Properties["MerchantId"] = merchant.Id;

            // validate api key:
            if (merchant != null)
            {
                return true;
            }

            return false;
        }


        protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
        {
            actionContext.Response = actionContext.Request
                .CreateResponse(HttpStatusCode.Unauthorized, "Invalid or missing API key");
        }
    }
}


// models:
public class LeadSubmitDto
{
    public int LeadId { get; set; }

    public EnumLeadStatus LeadStatus { get; set; }

}

public class ApiFeedback
{
    public int Id { get; set; }

    public int LeadId { get; set; }  // this can either be convero lead id, or buyer distribution id

    public EnumLeadStatus EnumLeadStatus { get; set; }

    public DateTime CreatedOn { get; set; }

    public int PostedByMerchantId { get; set; }   // Keep track of which merchant actually posted becuase a lead can be shared by multiple merchants
}


// business layer:
public class FeedbackBusinessLayer
{
    private ConveroReportingContext dbContext;

    public FeedbackBusinessLayer(ConveroReportingContext context)
    {
        this.dbContext = context;
    }


    public List<ApiFeedback> ApiFeedbacks()
    {
        return dbContext.ApiFeedbacks.ToList();
    }


    public void AddApiFeedback(ApiFeedback apiFeedback)
    {
        dbContext.ApiFeedbacks.Add(apiFeedback);

        dbContext.SaveChanges();
    }

}


// dbcontext:
public class ConveroReportingContext : DbContext
{
    public ConveroReportingContext() : base("name=ConveroReportingConnection")
    {
        this.Configuration.LazyLoadingEnabled = true;
    }

    public virtual DbSet<ApiFeedback> ApiFeedbacks { get; set; }
}


// web api controllers:
// cors is installed and enabled for all
namespace WebApi.LeadQService.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [ApiKeyAuthorize]
    [RoutePrefix("api/feedback")]   // single route for all in controller
    public class FeedbackController : ApiController
    {
        private ConveroReportingContext reportContext;

        public FeedbackController()
        {
            reportContext = new ConveroReportingContext();
        }


        [HttpPost]
        [Route("")]
        public IHttpActionResult Submit([FromBody] LeadSubmitDto dto)
        {
            if (dto == null || dto.LeadId == 0 || dto.LeadStatus == 0 )
            {
                return BadRequest("Bad request");
            }

            FeedbackBusinessLayer businessLayer = new FeedbackBusinessLayer(reportContext);

            ApiFeedback feedback = new ApiFeedback();
            feedback.LeadId = dto.LeadId;
            feedback.EnumLeadStatus = (EnumLeadStatus) dto.LeadStatus;
            feedback.CreatedOn = DateTime.Now;
            feedback.PostedByMerchantId = (int)Request.Properties["MerchantId"];

            businessLayer.AddApiFeedback(feedback);

            return Created($"api/feedback/{feedback.Id}", feedback);
        }


        // GET api/records
        [HttpGet]
        [Route("")]
        public IHttpActionResult GetLastWeek()
        {
            int merchantId = (int)Request.Properties["MerchantId"];

            FeedbackBusinessLayer businessLayer = new FeedbackBusinessLayer(reportContext);

            List<ApiFeedback> feedbacks = businessLayer.ApiFeedbacks()
                .Where(a => a.PostedByMerchantId == merchantId && a.CreatedOn > DateTime.Today.AddDays(-1)).ToList();

            return Ok(feedbacks);
        }
    }
}


// used to generate a random string for api key:
public class SecurityController : ApiController
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [HttpPost]
    [Route("api/security/generatekey")]
    public IHttpActionResult GenerateKey()
    {
        using (var rng = new RNGCryptoServiceProvider())
        {
            var bytes = new byte[32];

            rng.GetBytes(bytes);

            // convert to URL-safe Base64
            var key = Convert.ToBase64String(bytes)
                             .Replace("+", "")
                             .Replace("/", "")
                             .Replace("=", "");

            return Ok(key);
        }
    }

}

Created on: Thursday, January 22, 2026 by Andrew Sin