From 896985584c01d30723bb6af1a4ae5cf2455acbe6 Mon Sep 17 00:00:00 2001 From: arunmish Date: Fri, 22 May 2026 18:49:32 +0530 Subject: [PATCH 1/8] added control for IDOR vulnerability --- OAuthDemo/Controllers/DemoController.cs | 38 +++++++++++++++++++++---- OAuthDemo/Models/DemoModels.cs | 3 +- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/OAuthDemo/Controllers/DemoController.cs b/OAuthDemo/Controllers/DemoController.cs index f79a119..5dbe17f 100644 --- a/OAuthDemo/Controllers/DemoController.cs +++ b/OAuthDemo/Controllers/DemoController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Web; using System.Web.Mvc; +using Microsoft.AspNet.Identity; using IO.Swagger.Client; using IO.Swagger.Api; @@ -11,6 +12,7 @@ namespace OAuthDemo.Controllers { + [Authorize] public class DemoController : Controller { private readonly ApplicationDbContext _context; @@ -18,10 +20,19 @@ public DemoController() { _context = new ApplicationDbContext(); } + + // Helper method to verify ownership + private Demo GetUserDemo(string id) + { + var userId = User.Identity.GetUserId(); + return _context.Demos.SingleOrDefault(d => d.Id == id && d.UserId == userId); + } + // GET: Demo public ActionResult Index() { Demo DemoModel = new Demo(Guid.NewGuid().ToString()); + DemoModel.UserId = User.Identity.GetUserId(); _context.Demos.Add(DemoModel); _context.SaveChanges(); return View(DemoModel); @@ -36,7 +47,10 @@ public ActionResult RegisterApplication() public ActionResult RedirectMerchant(Demo InputModel) { System.Diagnostics.Debug.WriteLine(_context.Demos.ToString()); - var SavedModel = _context.Demos.SingleOrDefault(d => d.Id == InputModel.Id); + var SavedModel = GetUserDemo(InputModel.Id); + if (SavedModel == null) + return new HttpUnauthorizedResult(); + SavedModel.ClientId = InputModel.ClientId; SavedModel.RedirectUri = InputModel.RedirectUri; SavedModel.Read = InputModel.Read; @@ -52,7 +66,10 @@ public ActionResult RedirectMerchant(Demo InputModel) // step 3 public ActionResult RetrieveAccessToken(Demo InputModel) { - var SavedModel = _context.Demos.SingleOrDefault(d => d.Id == InputModel.Id); + var SavedModel = GetUserDemo(InputModel.Id); + if (SavedModel == null) + return new HttpUnauthorizedResult(); + SavedModel.GrantType = InputModel.GrantType; SavedModel.Code = InputModel.Code; SavedModel.ClientId = InputModel.ClientId; @@ -78,7 +95,10 @@ public ActionResult RetrieveAccessToken(Demo InputModel) // step 4 public ActionResult ChargeCreditCard(Demo InputModel) { - var SavedModel = _context.Demos.SingleOrDefault(d => d.Id == InputModel.Id); + var SavedModel = GetUserDemo(InputModel.Id); + if (SavedModel == null) + return new HttpUnauthorizedResult(); + SavedModel.AccessToken = InputModel.AccessToken; SavedModel.CardNumber = InputModel.CardNumber; SavedModel.ExpirationDate = InputModel.ExpirationDate; @@ -103,7 +123,10 @@ public ActionResult ChargeCreditCard(Demo InputModel) public ActionResult GetTransactionDetails(Demo InputModel) { - var SavedModel = _context.Demos.SingleOrDefault(d => d.Id == InputModel.Id); + var SavedModel = GetUserDemo(InputModel.Id); + if (SavedModel == null) + return new HttpUnauthorizedResult(); + SavedModel.AccessToken = InputModel.AccessToken; SavedModel.TransactionId = InputModel.TransactionId; @@ -126,7 +149,10 @@ public ActionResult GetTransactionDetails(Demo InputModel) // step 5 public ActionResult RefreshAccessToken(Demo InputModel) { - var SavedModel = _context.Demos.SingleOrDefault(d => d.Id == InputModel.Id); + var SavedModel = GetUserDemo(InputModel.Id); + if (SavedModel == null) + return new HttpUnauthorizedResult(); + SavedModel.ClientId = InputModel.ClientId; SavedModel.ClientSecret = InputModel.ClientSecret; SavedModel.GrantType = InputModel.GrantType; @@ -152,4 +178,4 @@ public ActionResult RedirectRevokePermissions() return Redirect(Demo.RevokePermissionsUrl); } } -} \ No newline at end of file +} diff --git a/OAuthDemo/Models/DemoModels.cs b/OAuthDemo/Models/DemoModels.cs index 508920d..f13b592 100644 --- a/OAuthDemo/Models/DemoModels.cs +++ b/OAuthDemo/Models/DemoModels.cs @@ -43,6 +43,7 @@ public Demo(string InputId) : this() } public string Id { get; set; } + public string UserId { get; set; } // Step 1 public string ClientId { get; set; } @@ -108,4 +109,4 @@ override public string ToString() "\n"; } } -} \ No newline at end of file +} From fdf0120ec9dddedb1f0a55463b658404fc254e02 Mon Sep 17 00:00:00 2001 From: arunmish Date: Fri, 22 May 2026 22:12:01 +0530 Subject: [PATCH 2/8] added minimal input for api to avoid consuming complete DemoInput --- OAuthDemo/Controllers/DemoController.cs | 60 ++++++++++++------------- OAuthDemo/Models/DemoInputModels.cs | 54 ++++++++++++++++++++++ 2 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 OAuthDemo/Models/DemoInputModels.cs diff --git a/OAuthDemo/Controllers/DemoController.cs b/OAuthDemo/Controllers/DemoController.cs index 5dbe17f..3478ea9 100644 --- a/OAuthDemo/Controllers/DemoController.cs +++ b/OAuthDemo/Controllers/DemoController.cs @@ -44,19 +44,19 @@ public ActionResult RegisterApplication() } // step 2 - public ActionResult RedirectMerchant(Demo InputModel) + public ActionResult RedirectMerchant(RedirectMerchantInput input) { System.Diagnostics.Debug.WriteLine(_context.Demos.ToString()); - var SavedModel = GetUserDemo(InputModel.Id); + var SavedModel = GetUserDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); - SavedModel.ClientId = InputModel.ClientId; - SavedModel.RedirectUri = InputModel.RedirectUri; - SavedModel.Read = InputModel.Read; - SavedModel.Write = InputModel.Write; - SavedModel.State = InputModel.State; - SavedModel.Sub = InputModel.Sub; + SavedModel.ClientId = input.ClientId; + SavedModel.RedirectUri = input.RedirectUri; + SavedModel.Read = input.Read; + SavedModel.Write = input.Write; + SavedModel.State = input.State; + SavedModel.Sub = input.Sub; SavedModel.updateRedirectMerchantUrl(); _context.SaveChanges(); @@ -64,16 +64,16 @@ public ActionResult RedirectMerchant(Demo InputModel) } // step 3 - public ActionResult RetrieveAccessToken(Demo InputModel) + public ActionResult RetrieveAccessToken(RetrieveAccessTokenInput input) { - var SavedModel = GetUserDemo(InputModel.Id); + var SavedModel = GetUserDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); - SavedModel.GrantType = InputModel.GrantType; - SavedModel.Code = InputModel.Code; - SavedModel.ClientId = InputModel.ClientId; - SavedModel.ClientSecret = InputModel.ClientSecret; + SavedModel.GrantType = input.GrantType; + SavedModel.Code = input.Code; + SavedModel.ClientId = input.ClientId; + SavedModel.ClientSecret = input.ClientSecret; try { @@ -93,16 +93,16 @@ public ActionResult RetrieveAccessToken(Demo InputModel) } // step 4 - public ActionResult ChargeCreditCard(Demo InputModel) + public ActionResult ChargeCreditCard(ChargeCreditCardInput input) { - var SavedModel = GetUserDemo(InputModel.Id); + var SavedModel = GetUserDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); - SavedModel.AccessToken = InputModel.AccessToken; - SavedModel.CardNumber = InputModel.CardNumber; - SavedModel.ExpirationDate = InputModel.ExpirationDate; - SavedModel.Amount = InputModel.Amount; + SavedModel.AccessToken = input.AccessToken; + SavedModel.CardNumber = input.CardNumber; + SavedModel.ExpirationDate = input.ExpirationDate; + SavedModel.Amount = input.Amount; try { @@ -121,14 +121,14 @@ public ActionResult ChargeCreditCard(Demo InputModel) return View("Index", SavedModel); } - public ActionResult GetTransactionDetails(Demo InputModel) + public ActionResult GetTransactionDetails(GetTransactionDetailsInput input) { - var SavedModel = GetUserDemo(InputModel.Id); + var SavedModel = GetUserDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); - SavedModel.AccessToken = InputModel.AccessToken; - SavedModel.TransactionId = InputModel.TransactionId; + SavedModel.AccessToken = input.AccessToken; + SavedModel.TransactionId = input.TransactionId; try { @@ -147,16 +147,16 @@ public ActionResult GetTransactionDetails(Demo InputModel) } // step 5 - public ActionResult RefreshAccessToken(Demo InputModel) + public ActionResult RefreshAccessToken(RefreshAccessTokenInput input) { - var SavedModel = GetUserDemo(InputModel.Id); + var SavedModel = GetUserDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); - SavedModel.ClientId = InputModel.ClientId; - SavedModel.ClientSecret = InputModel.ClientSecret; - SavedModel.GrantType = InputModel.GrantType; - SavedModel.RefreshToken = InputModel.RefreshToken; + SavedModel.ClientId = input.ClientId; + SavedModel.ClientSecret = input.ClientSecret; + SavedModel.GrantType = input.GrantType; + SavedModel.RefreshToken = input.RefreshToken; try { diff --git a/OAuthDemo/Models/DemoInputModels.cs b/OAuthDemo/Models/DemoInputModels.cs new file mode 100644 index 0000000..0667b18 --- /dev/null +++ b/OAuthDemo/Models/DemoInputModels.cs @@ -0,0 +1,54 @@ +using System; + +namespace OAuthDemo.Models +{ + // Input model for RedirectMerchant action + public class RedirectMerchantInput + { + public string Id { get; set; } + public string ClientId { get; set; } + public string RedirectUri { get; set; } + public bool Read { get; set; } + public bool Write { get; set; } + public string State { get; set; } + public string Sub { get; set; } + } + + // Input model for RetrieveAccessToken action + public class RetrieveAccessTokenInput + { + public string Id { get; set; } + public string GrantType { get; set; } + public string Code { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + } + + // Input model for ChargeCreditCard action + public class ChargeCreditCardInput + { + public string Id { get; set; } + public string AccessToken { get; set; } + public string CardNumber { get; set; } + public DateTime ExpirationDate { get; set; } + public decimal Amount { get; set; } + } + + // Input model for GetTransactionDetails action + public class GetTransactionDetailsInput + { + public string Id { get; set; } + public string AccessToken { get; set; } + public string TransactionId { get; set; } + } + + // Input model for RefreshAccessToken action + public class RefreshAccessTokenInput + { + public string Id { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string GrantType { get; set; } + public string RefreshToken { get; set; } + } +} From 7ebdd8523b832cac92f8dcd7b1c96a746354622c Mon Sep 17 00:00:00 2001 From: arunmish Date: Fri, 22 May 2026 22:29:35 +0530 Subject: [PATCH 3/8] token fields masked as password type --- OAuthDemo/Views/Demo/Index.cshtml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OAuthDemo/Views/Demo/Index.cshtml b/OAuthDemo/Views/Demo/Index.cshtml index bbc8370..70f69bd 100644 --- a/OAuthDemo/Views/Demo/Index.cshtml +++ b/OAuthDemo/Views/Demo/Index.cshtml @@ -36,7 +36,7 @@ @Html.LabelFor(m => m.ClientSecret, new {@class = "col-md-2 control-label"})
- @Html.TextBoxFor(m => m.ClientSecret, new {@class = "form-control", Value = Model.ClientSecret}) + @Html.PasswordFor(m => m.ClientSecret, new {@class = "form-control", Value = Model.ClientSecret})
@@ -122,7 +122,7 @@
@Html.LabelFor(m => m.ClientSecret, new {@class = "col-md-2 control-label"})
- @Html.TextBoxFor(m => m.ClientSecret, new {@class = "form-control"}) + @Html.PasswordFor(m => m.ClientSecret, new {@class = "form-control"})
@@ -160,11 +160,11 @@
@Html.LabelFor(m => m.AccessToken, new {@class = "col-md-2 control-label"})
- @Html.TextBoxFor(m => m.AccessToken, new {@class = "form-control"}) + @Html.PasswordFor(m => m.AccessToken, new {@class = "form-control"})
@Html.LabelFor(m => m.CardNumber, new {@class = "col-md-2 control-label"})
- @Html.TextBoxFor(m => m.CardNumber, new {@class = "form-control"}) + @Html.PasswordFor(m => m.CardNumber, new {@class = "form-control"})
@Html.LabelFor(m => m.ExpirationDate, new {@class = "col-md-2 control-label"})
@@ -195,7 +195,7 @@
@Html.LabelFor(m => m.AccessToken, new { @class = "col-md-2 control-label" })
- @Html.TextBoxFor(m => m.AccessToken, new {@class = "form-control"}) + @Html.PasswordFor(m => m.AccessToken, new {@class = "form-control"})
@Html.LabelFor(m => m.TransactionId, new { @class = "col-md-2 control-label" })
@@ -235,7 +235,7 @@
@Html.LabelFor(m => m.ClientSecret, new { @class = "col-md-2 control-label" })
- @Html.TextBoxFor(m => m.ClientSecret, new { @class = "form-control"}) + @Html.PasswordFor(m => m.ClientSecret, new { @class = "form-control"})
@Html.LabelFor(m => m.GrantType, new { @class = "col-md-2 control-label" })
@@ -243,7 +243,7 @@
@Html.LabelFor(m => m.RefreshToken, new { @class = "col-md-2 control-label" })
- @Html.TextBoxFor(m => m.RefreshToken, new { @class = "form-control" }) + @Html.PasswordFor(m => m.RefreshToken, new { @class = "form-control" })
From 08d13524055f4651dc1262af57ba4722472b2aee Mon Sep 17 00:00:00 2001 From: arunmish Date: Fri, 22 May 2026 23:33:50 +0530 Subject: [PATCH 4/8] oauth token security remediations --- OAuthDemo/Models/DemoModels.cs | 4 ++-- OAuthDemo/Views/Demo/Index.cshtml | 4 ++-- OAuthDemo/Web.config | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/OAuthDemo/Models/DemoModels.cs b/OAuthDemo/Models/DemoModels.cs index f13b592..f193d22 100644 --- a/OAuthDemo/Models/DemoModels.cs +++ b/OAuthDemo/Models/DemoModels.cs @@ -22,8 +22,8 @@ static Demo() public Demo() { - ClientId = "4dp5b7gRqk"; - ClientSecret = "fa3a5b16753d09b24bb44243605a4a98"; + ClientId = ConfigurationManager.AppSettings["ClientId"] ?? "4dp5b7gRqk"; + ClientSecret = ConfigurationManager.AppSettings["ClientSecret"] ?? ""; RedirectUri = "https://developer.authorize.net/api/reference/index.html"; Read = true; Write = true; diff --git a/OAuthDemo/Views/Demo/Index.cshtml b/OAuthDemo/Views/Demo/Index.cshtml index 70f69bd..e502d50 100644 --- a/OAuthDemo/Views/Demo/Index.cshtml +++ b/OAuthDemo/Views/Demo/Index.cshtml @@ -36,7 +36,7 @@
@Html.LabelFor(m => m.ClientSecret, new {@class = "col-md-2 control-label"})
- @Html.PasswordFor(m => m.ClientSecret, new {@class = "form-control", Value = Model.ClientSecret}) + @Html.PasswordFor(m => m.ClientSecret, new {@class = "form-control"})
@@ -134,7 +134,7 @@

Response:

- @Html.TextArea("Step3Response", null, new {rows = 10, cols = 500, value = Model.Step3Response}) + @Html.TextArea("Step3Response", null, new {rows = 10, cols = 500, value = Model.Step3Response, @readonly = "readonly"})
Continue diff --git a/OAuthDemo/Web.config b/OAuthDemo/Web.config index eab1933..d6dc2d3 100644 --- a/OAuthDemo/Web.config +++ b/OAuthDemo/Web.config @@ -13,6 +13,8 @@ + + @@ -91,4 +93,4 @@ - \ No newline at end of file + From bd496cd6586647cc59798fb15154f9913a03ae78 Mon Sep 17 00:00:00 2001 From: arunmish Date: Fri, 22 May 2026 23:43:47 +0530 Subject: [PATCH 5/8] warnings added for client auth token encryption --- OAuthDemo/Controllers/DemoController.cs | 2 ++ OAuthDemo/Models/DemoModels.cs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/OAuthDemo/Controllers/DemoController.cs b/OAuthDemo/Controllers/DemoController.cs index 3478ea9..39603a7 100644 --- a/OAuthDemo/Controllers/DemoController.cs +++ b/OAuthDemo/Controllers/DemoController.cs @@ -73,6 +73,8 @@ public ActionResult RetrieveAccessToken(RetrieveAccessTokenInput input) SavedModel.GrantType = input.GrantType; SavedModel.Code = input.Code; SavedModel.ClientId = input.ClientId; + // WARNING: Demo application only - credentials stored in plaintext for educational purposes + // PRODUCTION CODE MUST encrypt credentials at rest using ASP.NET Data Protection API or column-level encryption SavedModel.ClientSecret = input.ClientSecret; try diff --git a/OAuthDemo/Models/DemoModels.cs b/OAuthDemo/Models/DemoModels.cs index f193d22..5bd4665 100644 --- a/OAuthDemo/Models/DemoModels.cs +++ b/OAuthDemo/Models/DemoModels.cs @@ -7,6 +7,12 @@ namespace OAuthDemo.Models { + // WARNING: This is a demonstration application for educational purposes only + // OAuth credentials are stored in plaintext in the database to simplify the demo + // PRODUCTION APPLICATIONS MUST encrypt sensitive data at rest using: + // - ASP.NET Data Protection API + // - SQL Server column-level encryption + // - Azure Key Vault or similar key management service public class Demo { public const string RetrieveErrorResponse = "Error Retrieving the Access Token"; From acde5578aab46f4b093f1c81248d19e669bf980f Mon Sep 17 00:00:00 2001 From: arunmish Date: Thu, 28 May 2026 01:19:43 +0530 Subject: [PATCH 6/8] changes to remove hardcoded sandbox keys --- OAuthDemo/Models/DemoModels.cs | 5 +- OAuthDemo/Web.config | 9 +++- README.md | 49 ++++++++++++++----- .../Api/RetrievingrefreshingApiTests.cs | 10 ++-- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/OAuthDemo/Models/DemoModels.cs b/OAuthDemo/Models/DemoModels.cs index 5bd4665..5548831 100644 --- a/OAuthDemo/Models/DemoModels.cs +++ b/OAuthDemo/Models/DemoModels.cs @@ -28,7 +28,10 @@ static Demo() public Demo() { - ClientId = ConfigurationManager.AppSettings["ClientId"] ?? "4dp5b7gRqk"; + // ClientId / ClientSecret must come from Web.config (use placeholders + // [YOUR_CLIENT_ID] / [YOUR_CLIENT_SECRET] until you fill in your own sandbox values). + // Obtain values from the Authorize.Net Sandbox portal — see README for the procedure. + ClientId = ConfigurationManager.AppSettings["ClientId"] ?? ""; ClientSecret = ConfigurationManager.AppSettings["ClientSecret"] ?? ""; RedirectUri = "https://developer.authorize.net/api/reference/index.html"; Read = true; diff --git a/OAuthDemo/Web.config b/OAuthDemo/Web.config index d6dc2d3..f11d5f5 100644 --- a/OAuthDemo/Web.config +++ b/OAuthDemo/Web.config @@ -13,8 +13,13 @@ - - + + + diff --git a/README.md b/README.md index b43e523..55cbef2 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,52 @@ # OAuth Sample Application -This repository contains a sample application which demonstrates connecting to Authorize.Net using the OAuth 2.0 authentication standard. +This repository contains a **sample / educational application** that demonstrates how to integrate with the Authorize.Net **OAuth 2.0 sandbox** flow from an ASP.NET MVC application. It is **not a production application**, **not a hosted service**, and **not part of the main Authorize.Net product**. It is intended for developers who want to see, build, and run an OAuth 2.0 reference integration **locally** while learning the Authorize.Net APIs. + +> ### ⚠️ Scope & Intended Use +> +> - **Purpose:** Educational reference / integration showcase only. +> - **Deployment:** Designed to be cloned and run **locally** (Visual Studio + IIS Express). It is _not_ deployed to any hosted environment, and the project ships with no production URL or production endpoint. +> - **Backend target:** Authorize.Net **Sandbox only** (`https://sandbox.authorize.net`). There is no production URL anywhere in the codebase. +> - **Data:** Uses an attached `LocalDb` (`(LocalDb)\MSSQLLocalDB`) on the developer's own machine. No shared database, no production data. +> - **Credentials:** Sample / placeholder values only (e.g., `[YOUR_CLIENT_ID]`, `[YOUR_CLIENT_SECRET]`). Real credentials must be obtained by the developer from the Authorize.Net Sandbox portal and used **only locally** in `Web.config` ``. +> +> 📄 **Full security context, threat model, and AI SAST `poc_personal` validation guidance:** see [`SECURITY-CONTEXT.md`](./SECURITY-CONTEXT.md). ## **How to Use the Sample Application?** - Clone or download this repository. -- Open solution OAuthDemo.sln in Visual Studio and set OAuthDemo as StartUp project -- Run OAuthDemo.sln from Visual studio to launch the application. Application runs on local IIS server. +- Open solution OAuthDemo.sln in Visual Studio and set OAuthDemo as StartUp project. +- Open `OAuthDemo/Web.config` and **replace the `[YOUR_CLIENT_ID]` and `[YOUR_CLIENT_SECRET]` placeholders** with values you obtain from the Authorize.Net Sandbox portal (see below). +- Run OAuthDemo.sln from Visual studio to launch the application. Application runs on the local IIS Express server. -![alt text](https://github.com/AuthorizeNet/oauth-sample-app/blob/master/OAuthDemo/Screenshots/Image1.png ) +![alt text](https://github.com/AuthorizeNet/oauth-sample-app/blob/master/OAuthDemo/Screenshots/Image1.png) -- ClientId and ClientSecret values can be obtained by contacting Authorize.Net support team at [affiliate@authorize.net](mailto:affiliate@authorize.net) and providing a RedirectUri(This is the page that the merchant is redirected back to after granting permissions) for your application. +### Obtaining your sandbox `ClientId` and `ClientSecret` -Sample ClientId and ClientSecret shown in the below screen can be used for the demo purpose and can later be replaced in the code with newly obtained ClientId and ClientSecret. +You must use **your own** Authorize.Net **sandbox** credentials. This repository deliberately does **not** ship working credentials. -File - DemoModel.cs +1. Sign in to the Authorize.Net sandbox: **https://sandbox.authorize.net** +2. Navigate to **Account → Settings → Security Settings → API Credentials & Keys**. +3. Generate / copy your **Client ID** and **Client Secret**. +4. Provide a **RedirectUri** (the page the merchant is redirected back to after granting permissions) when registering your application. +5. Place the values into `OAuthDemo/Web.config`: -public Demo() +```xml + + +``` - { +> If you have any questions on credentialing, you can reach the Authorize.Net partner team at [affiliate@authorize.net](mailto:affiliate@authorize.net). - ClientId = "4dp5b7gRqk"; +`OAuthDemo/Models/DemoModels.cs` reads these values via `ConfigurationManager.AppSettings["ClientId"]` / `["ClientSecret"]`. The application falls back to an empty string if the key is missing — there are no hardcoded credentials anywhere in the source tree. - ClientSecret = "fa3a5b16753d09b24bb44243605a4a98"; +```csharp +public Demo() +{ + ClientId = ConfigurationManager.AppSettings["ClientId"] ?? ""; + ClientSecret = ConfigurationManager.AppSettings["ClientSecret"] ?? ""; + // … +} ![alt text](https://github.com/AuthorizeNet/oauth-sample-app/blob/master/OAuthDemo/Screenshots/Image2.png ) @@ -40,7 +64,7 @@ public Demo() ![alt text](https://github.com/AuthorizeNet/oauth-sample-app/blob/master/OAuthDemo/Screenshots/Image4.png ) -- Login with your Authorize.net credentials to allow access. +- Login with your Authorize.net credentials to allow access. ![alt text](https://github.com/AuthorizeNet/oauth-sample-app/blob/master/OAuthDemo/Screenshots/Image5.png ) - Click Allow. Page will be redirected to [https://developer.authorize.net](https://developer.authorize.net) with generated authorization code in the url. Copy the authorization code to obtain access and refresh token. @@ -111,3 +135,4 @@ Note: If the OAuthDemo application is running on a network which is behind a pro </defaultProxy> </system.net> +``` diff --git a/csharp-client/src/IO.Swagger.Test/Api/RetrievingrefreshingApiTests.cs b/csharp-client/src/IO.Swagger.Test/Api/RetrievingrefreshingApiTests.cs index a09a8d7..ad9b321 100644 --- a/csharp-client/src/IO.Swagger.Test/Api/RetrievingrefreshingApiTests.cs +++ b/csharp-client/src/IO.Swagger.Test/Api/RetrievingrefreshingApiTests.cs @@ -70,11 +70,13 @@ public void InstanceTest() [Test] public void GetTokenTest() { - // TODO uncomment below to test the method and replace null with proper value + // TODO uncomment below to test the method and replace placeholders with values + // obtained from the Authorize.Net Sandbox portal: + // https://sandbox.authorize.net → Account → Settings → Security Settings → API Credentials & Keys string grantType = "authorization_code"; - string clientId = "4dp5b7gRqk"; - string code = "novp2e"; - string clientSecret = "fa3a5b16753d09b24bb44243605a4a98"; + string clientId = "[YOUR_CLIENT_ID]"; + string code = "[YOUR_AUTHORIZATION_CODE]"; + string clientSecret = "[YOUR_CLIENT_SECRET]"; string refreshToken = null; int? platform = 2; var response = instance.GetToken(grantType, clientId, code, clientSecret, refreshToken, platform); From 8d0b915c15c13e534cbc7d1e75e2241668c0dac8 Mon Sep 17 00:00:00 2001 From: arunmish Date: Tue, 2 Jun 2026 19:37:39 +0530 Subject: [PATCH 7/8] fix: race condition for merchant auth fixed --- OAuthDemo/Controllers/ChargeCreditCard.cs | 18 +++++++++++------- OAuthDemo/Controllers/GetTransactionDetails.cs | 10 ++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/OAuthDemo/Controllers/ChargeCreditCard.cs b/OAuthDemo/Controllers/ChargeCreditCard.cs index 9f57332..d0f1272 100644 --- a/OAuthDemo/Controllers/ChargeCreditCard.cs +++ b/OAuthDemo/Controllers/ChargeCreditCard.cs @@ -15,10 +15,10 @@ public static String Run(String AccessToken, String CardNumber, DateTime Expirat { Console.WriteLine("Charge Credit Card Sample"); - ApiOperationBase.RunEnvironment = AuthorizeNet.Environment.SANDBOX; - - // define the merchant information (authentication / transaction id) - ApiOperationBase.MerchantAuthentication = new merchantAuthenticationType() + // SECURITY: Authentication is set per-request on the request object (not via + // shared static properties) to prevent race conditions in multi-threaded + // ASP.NET environments. Environment is passed to Execute() for the same reason. + var merchantAuthentication = new merchantAuthenticationType() { ItemElementName = ItemChoiceType.accessToken, Item = AccessToken, @@ -58,11 +58,15 @@ public static String Run(String AccessToken, String CardNumber, DateTime Expirat lineItems = lineItems }; - var request = new createTransactionRequest { transactionRequest = transactionRequest }; + var request = new createTransactionRequest + { + merchantAuthentication = merchantAuthentication, + transactionRequest = transactionRequest + }; - // instantiate the contoller that will call the service + // instantiate the controller that will call the service var controller = new createTransactionController(request); - controller.Execute(); + controller.Execute(AuthorizeNet.Environment.SANDBOX); // get the response from the service (errors contained if any) var response = controller.GetApiResponse(); diff --git a/OAuthDemo/Controllers/GetTransactionDetails.cs b/OAuthDemo/Controllers/GetTransactionDetails.cs index cd2f507..a166a58 100644 --- a/OAuthDemo/Controllers/GetTransactionDetails.cs +++ b/OAuthDemo/Controllers/GetTransactionDetails.cs @@ -15,20 +15,22 @@ public static String Run(String AccessToken, string transactionId) { Console.WriteLine("Get transaction details sample"); - ApiOperationBase.RunEnvironment = AuthorizeNet.Environment.SANDBOX; - // define the merchant information (authentication / transaction id) - ApiOperationBase.MerchantAuthentication = new merchantAuthenticationType() + // SECURITY: Authentication is set per-request on the request object (not via + // shared static properties) to prevent race conditions in multi-threaded + // ASP.NET environments. Environment is passed to Execute() for the same reason. + var merchantAuthentication = new merchantAuthenticationType() { ItemElementName = ItemChoiceType.accessToken, Item = AccessToken }; var request = new getTransactionDetailsRequest(); + request.merchantAuthentication = merchantAuthentication; request.transId = transactionId; // instantiate the controller that will call the service var controller = new getTransactionDetailsController(request); - controller.Execute(); + controller.Execute(AuthorizeNet.Environment.SANDBOX); // get the response from the service (errors contained if any) var response = controller.GetApiResponse(); From dca63b761fce55f900bc41d7543ae22b12697ca3 Mon Sep 17 00:00:00 2001 From: arunmish Date: Tue, 7 Jul 2026 18:15:11 +0530 Subject: [PATCH 8/8] fix:proper IDOR fixes with Readme updates --- OAuthDemo/Controllers/DemoController.cs | 45 ++++++++++++++++++------- OAuthDemo/Models/DemoModels.cs | 1 - README.md | 17 ++++------ 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/OAuthDemo/Controllers/DemoController.cs b/OAuthDemo/Controllers/DemoController.cs index 39603a7..af59b8d 100644 --- a/OAuthDemo/Controllers/DemoController.cs +++ b/OAuthDemo/Controllers/DemoController.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Web; using System.Web.Mvc; -using Microsoft.AspNet.Identity; using IO.Swagger.Client; using IO.Swagger.Api; @@ -12,29 +11,51 @@ namespace OAuthDemo.Controllers { - [Authorize] + // This educational demo is intentionally usable without a login. To still prevent + // IDOR (one visitor tampering with a Demo record created by another visitor), each + // Demo is bound to the browser Session that created it, and every action validates + // that the requested Id belongs to the current session before touching it. public class DemoController : Controller { + private const string OwnedDemoIdsKey = "OwnedDemoIds"; + private readonly ApplicationDbContext _context; public DemoController() { _context = new ApplicationDbContext(); } - // Helper method to verify ownership - private Demo GetUserDemo(string id) + // The set of Demo Ids created by (and therefore owned by) the current session. + private HashSet OwnedDemoIds + { + get + { + var owned = Session[OwnedDemoIdsKey] as HashSet; + if (owned == null) + { + owned = new HashSet(); + Session[OwnedDemoIdsKey] = owned; + } + return owned; + } + } + + // Returns the Demo only if it belongs to the current session; otherwise null, + // so callers can reject the request instead of acting on someone else's record. + private Demo GetOwnedDemo(string id) { - var userId = User.Identity.GetUserId(); - return _context.Demos.SingleOrDefault(d => d.Id == id && d.UserId == userId); + if (string.IsNullOrEmpty(id) || !OwnedDemoIds.Contains(id)) + return null; + return _context.Demos.SingleOrDefault(d => d.Id == id); } // GET: Demo public ActionResult Index() { Demo DemoModel = new Demo(Guid.NewGuid().ToString()); - DemoModel.UserId = User.Identity.GetUserId(); _context.Demos.Add(DemoModel); _context.SaveChanges(); + OwnedDemoIds.Add(DemoModel.Id); // record session ownership for IDOR checks return View(DemoModel); } @@ -47,7 +68,7 @@ public ActionResult RegisterApplication() public ActionResult RedirectMerchant(RedirectMerchantInput input) { System.Diagnostics.Debug.WriteLine(_context.Demos.ToString()); - var SavedModel = GetUserDemo(input.Id); + var SavedModel = GetOwnedDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); @@ -66,7 +87,7 @@ public ActionResult RedirectMerchant(RedirectMerchantInput input) // step 3 public ActionResult RetrieveAccessToken(RetrieveAccessTokenInput input) { - var SavedModel = GetUserDemo(input.Id); + var SavedModel = GetOwnedDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); @@ -97,7 +118,7 @@ public ActionResult RetrieveAccessToken(RetrieveAccessTokenInput input) // step 4 public ActionResult ChargeCreditCard(ChargeCreditCardInput input) { - var SavedModel = GetUserDemo(input.Id); + var SavedModel = GetOwnedDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); @@ -125,7 +146,7 @@ public ActionResult ChargeCreditCard(ChargeCreditCardInput input) public ActionResult GetTransactionDetails(GetTransactionDetailsInput input) { - var SavedModel = GetUserDemo(input.Id); + var SavedModel = GetOwnedDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); @@ -151,7 +172,7 @@ public ActionResult GetTransactionDetails(GetTransactionDetailsInput input) // step 5 public ActionResult RefreshAccessToken(RefreshAccessTokenInput input) { - var SavedModel = GetUserDemo(input.Id); + var SavedModel = GetOwnedDemo(input.Id); if (SavedModel == null) return new HttpUnauthorizedResult(); diff --git a/OAuthDemo/Models/DemoModels.cs b/OAuthDemo/Models/DemoModels.cs index 5548831..3763dbe 100644 --- a/OAuthDemo/Models/DemoModels.cs +++ b/OAuthDemo/Models/DemoModels.cs @@ -52,7 +52,6 @@ public Demo(string InputId) : this() } public string Id { get; set; } - public string UserId { get; set; } // Step 1 public string ClientId { get; set; } diff --git a/README.md b/README.md index 55cbef2..d69aeb6 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ This repository contains a **sample / educational application** that demonstrate > - **Backend target:** Authorize.Net **Sandbox only** (`https://sandbox.authorize.net`). There is no production URL anywhere in the codebase. > - **Data:** Uses an attached `LocalDb` (`(LocalDb)\MSSQLLocalDB`) on the developer's own machine. No shared database, no production data. > - **Credentials:** Sample / placeholder values only (e.g., `[YOUR_CLIENT_ID]`, `[YOUR_CLIENT_SECRET]`). Real credentials must be obtained by the developer from the Authorize.Net Sandbox portal and used **only locally** in `Web.config` ``. -> -> 📄 **Full security context, threat model, and AI SAST `poc_personal` validation guidance:** see [`SECURITY-CONTEXT.md`](./SECURITY-CONTEXT.md). ## **How to Use the Sample Application?** @@ -47,6 +45,7 @@ public Demo() ClientSecret = ConfigurationManager.AppSettings["ClientSecret"] ?? ""; // … } +``` ![alt text](https://github.com/AuthorizeNet/oauth-sample-app/blob/master/OAuthDemo/Screenshots/Image2.png ) @@ -126,13 +125,11 @@ Refresh Token is revoked immediately. Any previously issued Access Token will be -Note: If the OAuthDemo application is running on a network which is behind a proxy, you may have to add below settings in the web.config file of the OAuth Demo application project to access the API endpoint. - -<system.net> +Note: If the OAuthDemo application is running on a network which is behind a proxy, you may have to add the settings below to the `Web.config` file of the OAuth Demo application project to access the API endpoint. - <defaultProxyuseDefaultCredentials="true"enabled="true"> - - </defaultProxy> - -</system.net> +```xml + + + + ```