Menangani Error: The provided anti-forgery token was meant for a different claims-based user than the current user.

Hari ini saya mendapatkan ticket dari Tester mengenai error authentikasi user. Test case nya seperti ini.

  1. Navigasi ke halaman Login di tab pertama, login sebagai user ‘A’.
  2. Kemudian buka tab baru pada browser, buka home page (selain halaman login).
  3. Logout user di tab pertama.
  4. Buka halaman lain pada tab 2, page akan redirect ke halaman login dengan membawa serta query string return url.
  5. Login di tab pertama.
  6. ketika login di tab kedua akan muncul error “The provided anti-forgery token was meant for a different claims-based user than the current user”.

Pertama saya mencoba dengan menonaktifkan cache pada action login  dengan menambahkan attribut [OutputCache(NoStore = true, Location = OutputCacheLocation.None)] tapi ternyata hasil nya tetap sama.

Cara kedua, yaitu dengan cara menghilangkan [ValidateAntiForgeryToken] pada action login, tapi tetap menambahkan  @Html.AntiForgeryToken() pada form. Tapi cara ini malah mengurangi tingkat keamanan pada website.

Akhirnya, setelah googiling. Saya menemukan solusi yang lebih tepat dengan cara membuat attribute untuk menangani error tersebut. Sehingga jika terjadi error tersebut bisa dialihkan ke halaman lain.

public class AntiForgeryHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is HttpAntiForgeryException)
{
var url = string.Empty;
if (!context.HttpContext.User.Identity.IsAuthenticated)
{
var requestContext = new RequestContext(context.HttpContext, context.RouteData);
url = RouteTable.Routes.GetVirtualPath(requestContext, new RouteValueDictionary(new { Controller = "User", action = "Login" })).VirtualPath;
}
else
{
context.HttpContext.Response.StatusCode = 200;
context.ExceptionHandled = true;
url = GetRedirectUrl(context);
}
context.HttpContext.Response.Redirect(url, true);
}
else
{
base.OnException(context);
}
}

private string GetRedirectUrl(ExceptionContext context)
{
try
{
var requestContext = new RequestContext(context.HttpContext, context.RouteData);
var url = RouteTable.Routes.GetVirtualPath(requestContext, new RouteValueDictionary(new { Controller = "Home", action = "Index" })).VirtualPath;

return url;
}
catch (Exception)
{
throw new NullReferenceException();
}
}

Tambahkan attribute tersebut pada login action

[HttpPost]
       [AllowAnonymous]
       [ValidateAntiForgeryToken]
       [AntiForgeryHandleError]
       public async Task Login(LoginViewModel model, string returnUrl)
       {
Iklan

Behavior Driven Development (BDD) Using SpecFlow

Behavior Driven Development (or BDD) is an Agile software development technique that encourages collaboration between developers, QA, and non-technical or business participants in a software project. 

Dalam BDD kita menggunakan aspek Given/When/Then ketika membuat Feature. Dari Feature ini,  Tester dapat membuat scenario test. Sedangkan  bagi Developer bisa digunakan untuk membuat Unit Testing. Hindari menggunakan terlalu banyak istilah teknis di dalam feature yang kita buat. Salah satu tujuan menggunakan BDD adalah agar menghindari salah pengertian mengenai sebuah feature antara tester, analis dan developer. Kita akan menggunakan SpecFlow sebagai framework BDD kita.

Sebagai contoh kita mempunyai story seperti ini. Story ini dibuat menggunakan SpecFlow feature file.

Feature: Add Vehicle
    As an Administrator
    I want to be to add new vehicle
    In order to a Customer can reserve it

@mytag
Scenario: Vehicle Name is not blank
    Given I have entered '' into the field name
    When I press Submit
    Then I see message saying name is mandatory

Kemudian klik kanan file feature yang kita buat untuk membuat step.

right_click_generate_steps

steps_bdd.png

SpecFlow akan menghasilkan file steps seperti ini.

using System;
using TechTalk.SpecFlow;

namespace BDDTest
{
[Binding]
public class AddVehicleSteps
{
[Given(@"I have entered '(.*)' into the field name")]
public void GivenIHaveEnteredIntoTheFieldName(string p0)
{
ScenarioContext.Current.Pending();
}

[When(@"I press Submit")]
public void WhenIPressSubmit()
{
ScenarioContext.Current.Pending();
}

[Then(@"I see message saying name is mandatory")]
public void ThenISeeMessageSayingNameIsMandatory()
{
ScenarioContext.Current.Pending();
}
}
}

Kita running unit step tersebut, terlihat bahwa unit tersebut diskip karena belum ada implementasi di dalamnya.

skipped_test.png

Kita akan membuat class untuk menghandle bisnis logic untuk feature tersebut dan memodifikasi class steps di atas.

using BDDProject;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using TechTalk.SpecFlow;

namespace BDDTest
{
[Binding]
public class AddVehicleSteps
{
private IVehicleBLL _vechileBll = new VehicleBLL();
private Vehicle _vehicle = new Vehicle();
private string _message;
[Given(@"I have entered '(.*)' into the field name")]
public void GivenIHaveEnteredIntoTheFieldName(string EmptyName)
{
_vehicle.Name = EmptyName;
}

[When(@"I press Submit")]
public void WhenIPressSubmit()
{
try
{
_vechileBll.Add(_vehicle);
}
catch (Exception ex)
{
_message = ex.Message;
}
}

[Then(@"I see message saying name is mandatory")]
public void ThenISeeMessageSayingNameIsMandatory()
{
Assert.AreEqual(_message.ToLower(), "name is mandatory");
}
}
}

Kita jalankan lagi unit test kita, dan hasilnya seperti ini.

bdd_output.png

Refactoring Using CodeMaid

CodeMaid adalah ekstensi open source untuk visual studio yang bisa digunakan untuk melihat tingkat kompleksitas code program kita selain melakukan formating dan mengorganisir kode. Semakin tinggi nilai kompleksitas sebuah fungsi, semakin butuh fungsi itu untuk direfactor. Karena salah satu prinsip clean code adalah satu method harusnya hanya memiliki 1 high level abstraksi.

contoh code dengan kompleksitas yang tinggi seperti di bawah ini.

private Expression<Func<Vehicle, bool>> CreateVehicleFilter(VehicleFilterInput input)
{
Expression<Func<Vehicle, bool>> queryFilter = PredicateHelper.True();

if (!string.IsNullOrEmpty(input.Name))
queryFilter = queryFilter.And(ve => ve.VehicleTranslations.Any(t => t.Language == input.Language) ?
ve.VehicleTranslations.Any(t => t.Language == input.Language && t.TranslatedText.ToLower().Contains(input.Name.ToLower()))
: ve.Name.ToLower().Contains(input.Name.ToLower()));

if (!string.IsNullOrEmpty(input.InternalNumber))
queryFilter = queryFilter.And(ve => ve.InternalNumber.ToLower().Contains(input.InternalNumber.ToLower()));

if (!string.IsNullOrEmpty(input.ChassisNumber))
queryFilter = queryFilter.And(ve => ve.Chassis.ToLower().Contains(input.ChassisNumber.ToLower()));

if (input.VehicleBrandId != null && input.VehicleBrandId != 0)
queryFilter = queryFilter.And(ve => ve.VehicleModel.VehicleBrand.Id == input.VehicleBrandId);

if (input.VehicleModelId != null && input.VehicleModelId != 0)
queryFilter = queryFilter.And(ve => ve.VehicleModel.Id == input.VehicleModelId);

if (input.DateOfEntry != null && input.DateOfEntry != DateTime.MinValue &&
input.DateOfEntryTo != null && input.DateOfEntryTo != DateTime.MinValue)
queryFilter = queryFilter.And(ve => ve.DateEntry >= input.DateOfEntry && ve.DateEntry <= input.DateOfEntryTo); else { if (input.DateOfEntry != null && input.DateOfEntry != DateTime.MinValue) queryFilter = queryFilter.And(ve => ve.DateEntry >= input.DateOfEntry);
if (input.DateOfEntryTo != null && input.DateOfEntryTo != DateTime.MinValue)
queryFilter = queryFilter.And(ve => ve.DateEntry <= input.DateOfEntryTo); } if (input.ReleaseDate != null && input.ReleaseDate != DateTime.MinValue && input.ReleaseDateTo != null && input.ReleaseDateTo != DateTime.MinValue) { queryFilter = queryFilter.And(ve => (ve.DateActualRelease >= input.ReleaseDate && ve.DateActualRelease <= input.ReleaseDateTo) || (ve.DateSupposedRelease >= input.ReleaseDate && ve.DateSupposedRelease <= input.ReleaseDateTo)); } else { if (input.ReleaseDate != null && input.ReleaseDate != DateTime.MinValue) { queryFilter = queryFilter.And(ve => (ve.DateActualRelease >= input.ReleaseDate) || (ve.DateSupposedRelease >= input.ReleaseDate));
}
if (input.ReleaseDateTo != null && input.ReleaseDateTo != DateTime.MinValue)
{
queryFilter = queryFilter.And(ve => (ve.DateActualRelease <= input.ReleaseDateTo) || (ve.DateSupposedRelease <= input.ReleaseDateTo)); } } if (!string.IsNullOrEmpty(input.Status)) queryFilter = queryFilter.And(ve => ve.Status.ToLower().Contains(input.Status.ToLower()));

return queryFilter;
}

Terlihat pada CodeMaid Spade di Visual Studio fungsi tersebut kompleksitasnya sangat tinggi, di atas nilai 10.

code_main_high_complexity

Kita refactor, dengan membaginya ke dalam beberapa fungsi yang lebih kecil seperti ini.

private Expression<Func<Vehicle, bool>> CreateVehicleFilter(VehicleFilterInput input)
{
Expression<Func<Vehicle, bool>> queryFilter = PredicateHelper.True();

FilterVehicleByName(input, ref queryFilter);
FilterByDateOfEntry(input, ref queryFilter);
FilterByChassisNumber(input, ref queryFilter);
FilterByInternalNumber(input, ref queryFilter);
FilterByVehicleBrand(input, ref queryFilter);
FilterByVehicleModel(input, ref queryFilter);
FilterByRangeReleaseDate(input, ref queryFilter);
FilterByStatus(input, ref queryFilter);
return queryFilter;

}

CodeMaid akan menunjukan bahwa tingkat kompleksitasnya sekarang jauh lebih sedikit.

code_main_low_complexity

Error dialog when Open .cshtml File

Saya mendapat error pagi ini. Ketika melakukan double click pada View (.cshtml) file di Asp.Net MVC project pada Visual Studio, muncul popup dialog dengan pesan error. Setelah googling, ternyata untuk mensolve issue ini caranya sangat mudah.

  1. Tutup Visual Studio
  2. Hapus semua file cache pada folder %LocalAppData%\Microsoft\VisualStudio\14.0\ComponentModelCache
  3. Buka kembali Visual Studio. Harusnya tidak ada lagi error seperti sebelumnya.

Mencari Hakikat Penciptaan

Sebagai manusia dan tentunya sebagai seorang theist. Saya selalu selalu mencari jawaban tentang tujuan penciptaan, juga tentang hubungan antara Sang Pencipta dan Ciptaan-Nya. Apakah Tuhan hanya hanya dipandang sebagai dzat yang transendent-yang terpisah dari makhluknya atau Tuhan selalu menampakan bayangan-Nya dibalik ciptaan-Nya.

Dalam sebuah hadist Qudsi yang kira-kira redaksinya seperti ini: Tuhan berkata bahwa Dia adalah perbendaharaan yang tersembunyi, yang rindu untuk di kenal maka Dia menciptakan makhluk. Begitupun firmannya dalam kitab suci: Aku lebih dekat daripada urat nadi kalian sendiri.

Bukankah Tuhan tidak butuh makhluknya? Terus buat apa Dia mencipta? Apalah artinya kedermawanan tanpa si miskin kata Rumi sang sufi penyair itu. Mungkin begitupun dengan Tuhan, dia dengan ‘kesadaran’ memahmi bahwa sifat-sifat agung-Nya hanya akan bisa termanifestasi jika ada object penerima. Maka, menciptalah Dia.

Jika membahas keberadaan dzat-Nya, esensi diri-Nya. Tentulah nalar kemanusiaan kita tak sanggup sampai kesana. Dzatnya adalah mutlak, gaib di atas gaib, misteri di atas misteri. Dengan Kasih Sayang-Nya, kemudian Dia sendirilah yang akhirnya memperkenalkan diri lewat sifat-sifatNya (kita menyebutnya sebagai Ilahi atau Allah) atau dengan perbuatanNya (kita menyebut-Nya Tuhan atau Rabb).

Beberapa orang menemukan Dia lewat fenomena ciptaan-Nya, lewat hukum-hukum alam yang dibuat-Nya begitu kompleks, rumit tapi berpola.

Beberapa lagi ketika dalam keadaan fana, menemukan bahwa hanya Dia lah sebenarnya yang tampak dan yang lain hanyalah memantulkan bayangan-Nya. Sehingga ketika kembali dari fana, bagi mereka, realitas yang tampak hanyalah semu belaka. Ada juga golongan yang berpendapat bahwa memang benar bahwa ciptaan-Nya memantulkan bayangan diri-Nya tapi mengingkari keberadaan object pemantul itu sendiri adalah kesalahan. Bagi golongan ini Dia dan ciptaan-Nya saling memantulkan.

Kalau ibarat cahaya, Dia memancarkan sinarnya keseluruh semesta dan seluruh ciptaanNya meneriman cahaya tersebut sesuai dengan jarak kepada-Nya. Semakin dekat semakin terang, semakin jauh semakin gelap. Tak ada gelap, yang ada hanya tak sempurnanya kehadiran-Nya.

Tugas kita sebagai manusia, makhluk yang paling sempurna memantulkan dan menyerap seluruh sifat-sifatNya adalah berakhlak dengan akhlak-Nya. Kata Ibnu Arabi bukan posisi manusia untuk menanamkan sifat-sifat Tuhan itu kedalam dirinya, karena sebenarnya dalam dirinya sudah ada potensi sifat-sifat Tuhan ketika Tuhan meniupkan ruh kepadanya.

Yang sering diabaikan, Non-functional Requirement

Ketika terlibat dalam sebuah team project pengembangan perangkat lunak, kita sebagai developer seringkali terlalu focus pada low level detail  sehingga lupa melihat big-picture berupa non-functional requirement seperti performance, scalability, availability dan security.

Performance

Performance bicara tentang seberapa cepat aplikasi tersebut, biasanya kita menggunakan term response time atau latency.Response time adalah waktu yang dibutuhkan antara sebuah request dikirim dan response diterima secara penuh. Seperti ketika kita menekan tombol hyperlink pada sebuah halaman web dan menunggu halaman page tersebut di load secara keseluruhan. Sedangkan latency adalah waktu yang diperlukan sebuah pesan untuk berpindah dalam sebuah system, dari suatu titik ke titik yang lain. Latency mengukur berapa besar waktu ketika response baru saja diterima. Sehingga Response Time akan selalu lebih besar daripada Latency.

Scalability

Scalability pada dasarnya kemampuan software untuk menangani lebih banyak pengguna, request, data dan sebagainya dalam periode waktu yang sama, misalanya berapa request per second.

Availability

Availability adalah tentang bagaimana tingkat ketersediaan (available) software untuk menangani request.  Biasanya diukur menggunakan terminasi “nines”. Seperti 99,99% (four nine) atau 99,999% (five nine).Angka-angka tersebut adalah angka uptime yang menunjukan berapa besar jumlah downtime yang bisa ditoleransi. Contoh, 99.9% (three nines) artinya downtime berkisar hanya 1 menit per harinya yang digunakan untuk jadwal maintenance berkala, upgrade atau memang terjadi kesalahan yang tidak diharapkan.

Security

Security berkaitan dengan segala sesuatu dari authentikasi dan authorisasi sampai pada bagaimana data tersebut disimpan. Authentikasi adalam proses memverifikasi user lewat credential yang dikirim. Sedangkan authorisasi terjadi setelah authentikasi, untuk membatasi resource mana yang boleh diakses oleh user  (file, url dan sebagainya).

Audit

Seringkali kita butuh untuk menyimpan log sebuah event yang bisa membantu kita untuk melakukan pelacakan perubahan sebuah data dari software system, terutama yang menyangkut transaksi yang melibatkan “uang”.  Bisanya sih disebut Audit Trail, dimana kita menyimpan data siapa yang melakukan perubahan, kapan perubahan itu terjadi dan apa saja yang berubah record yang berubah.

 

Flexibility

Flexibility adalah mengenai bagaimana software yang kita bangun memudahkan non-techincal user untuk memodifikasi business rules yang ada pada software. Sebagai contoh, kita bisa memasukkan value-value yang gampang dikonfigurasi di database ataupun diset lewat file configurasi seperti connection string, base url address, smpt server dan sebagainya.

Dan masih ada beberapa hal yang lain lagi seperti Maintability, Legal dan Localization..