Skip to content

Commit 06851a6

Browse files
committed
create security/antiforgery component
1 parent ddb815b commit 06851a6

File tree

6 files changed

+261
-1
lines changed

6 files changed

+261
-1
lines changed

src/app/security/antiforgery/antiforgery.component.css

Whitespace-only changes.
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<div class="container pt-3">
2+
<div class="row">
3+
<div id="sidebar" style="width: 260px;">
4+
<app-sidebar></app-sidebar>
5+
</div>
6+
<div id="content" class="flex-grow-1" style="width: 400px;">
7+
<article>
8+
<h1>CSRF Prevention</h1>
9+
<hr>
10+
<p>
11+
Cross-site request forgery, abbreviated as CSRF or XSRF and also known as a one-click attack or session riding, is a malicious attack that takes advantage of a user's previously authenticated session to execute unwanted actions by manipulating the interaction between a client browser and a trusted web application.
12+
</p>
13+
<p>
14+
To better understand the CSRF attack, consider the following scenario:
15+
</p>
16+
<ul>
17+
<li class="mb-2">
18+
A user signs into his account on a vulnerable website, which trusts any request received with a valid authentication cookie.
19+
</li>
20+
<li class="mb-2">
21+
Then, the user visits a malicious site that contains a fake HTML form to win a prize, but in the background, it posts to the vulnerable website like the following example:
22+
</li>
23+
</ul>
24+
<pre><code class="language-html">&lt;h1>Congratulations! You're a Winner!&lt;/h1>
25+
&lt;form method="post" action="https://example.com/account/edit">
26+
&lt;input type="hidden" name="email" value="[email protected]" />
27+
&lt;input type="submit" value="Collect the prize!" />
28+
&lt;/form>
29+
</code></pre>
30+
<ul>
31+
<li class="mb-2">
32+
When the user clicks on the submit button. The browser sends a request that includes the authentication cookie for the requested domain.
33+
</li>
34+
<li class="mb-2">
35+
The vulnerable server trusts the request with the authentication context and allows any action that an authenticated user can perform.
36+
</li>
37+
</ul>
38+
<p>
39+
In addition to this scenario, the malicious site could run a script that automatically submits the form by sending the form submission as an AJAX request.
40+
</p>
41+
<p>
42+
To prevent cross-site request forgery attacks, the DevNet framework provides the <code>Antiforgery</code> service that generates a CSRF token, which should be included in the form data or in the request header to be verified by the server when the form is submitted.
43+
</p>
44+
<br>
45+
<h3>Configuration</h3>
46+
<p>
47+
To use the <code>Antiforgory</code> service across your application, you need to register it as a dependency in your application services with the help of the extension method <code>addAntiforgory()</code> inside the method <code>register()</code> of the <code>WebHostBuilder</code> and have the option to customize the default configurations.
48+
</p>
49+
<pre><code class="language-php">&lt;?php
50+
namespace Application;
51+
52+
use DevNet\Web\Hosting\WebHost;
53+
use DevNet\Web\Extensions\ApplicationBuilderExtensions;
54+
use DevNet\Web\Extensions\ServiceCollectionExtensions;
55+
56+
class Program
57+
&lcub;
58+
public static function main(array $args = [])
59+
&lcub;
60+
$builder = WebHost::createDefaultBuilder($args);
61+
$builder->register(function ($services) &lcub;
62+
// Add antiforgery service.
63+
$services->addAntiforgery(function ($options) &lcub;
64+
// Optimally you can modify the following default options.
65+
$options->FieldName = "X-CSRF-TOKEN";
66+
$options->HeaderName = "X-XSRF-TOKEN";
67+
// If an cookie name is not provided, the system will generate a unique name.
68+
$options->CookieName = null;
69+
});
70+
});
71+
...
72+
}
73+
}
74+
</code></pre>
75+
<br>
76+
<h3>X-CSRF-TOKEN</h3>
77+
<p>
78+
In traditional HTML-based applications, the Antiforgery tokens are passed to the server using hidden form fields, and usually, the token used in this technique is called <samp>X-CSRF-TOKEN</samp>.
79+
</p>
80+
<p>
81+
After registering the Antiforgery service with the MVC web application, it will be injected into the view so that you can generate a token in the HTML form.
82+
</p>
83+
<pre><code class="language-php-template">&lt;form method="POST" action="/account/update/<?= $this->Model->Id ?>">
84+
&lt;input type="text" name="username value="<?= $this->Model->Username ?>" />
85+
&lt;input type="email" name="email" value="<?= $this->Model->Email ?>" />
86+
&lt;input type="hidden" name="<?= $this->Antiforgery->FieldName ?>" value="<?= $this->Antiforgery ?>" />
87+
&lt;/form>
88+
</code></pre>
89+
<p>
90+
The controller in this example sends the view that is presented above with an Antiforgory token via the <code>edit()</code> method when the user requests to edit his account, and when the user submits the HTML form, the controller receives the request via the <code>update()</code> method, which is decorated with the <code>DevNet\Web\Action\Filters\AntiForgery</code> attribute to check if the request is trusted before updating the data.
91+
</p>
92+
<pre><code class="language-php">&lt;?php
93+
namespace Application\Controllers;
94+
95+
use DevNet\System\Linq;
96+
use DevNet\Web\Endpoint\IActionResult;
97+
use DevNet\Web\Endpoint\ActionController;
98+
use DevNet\Web\Endpoint\Filters\AntiForgery;
99+
use DevNet\Web\Endpoint\Filters\Authorize;
100+
use DevNet\Web\Routting\Attributes\Get;
101+
use DevNet\Web\Routting\Attributes\Post;
102+
103+
#[Authorize]
104+
class AccountController extends ActionController
105+
&lcub;
106+
#[Get(['/account/edit/&lcub;id}'])]
107+
public function edit(int $id): IActionResult
108+
&lcub;
109+
$json = file_get_contents(__DIR__ . '/path/to/data.json');
110+
$data = json_decode($json, true);
111+
$users = new ArrayList('object');
112+
$users->addRange($data);
113+
114+
$user = $users->where(fn ($user) => $user->Id == $id)->first();
115+
if (!$user) &lcub;
116+
Throw \Exception("page not found!", 404);
117+
}
118+
return $this->view($user);
119+
}
120+
121+
// add AntiForgery filter as attribute.
122+
#[AntiForgery]
123+
#[Post(['/account/update/&lcub;id}'])]
124+
public function update(int $id, User $form): IActionResult
125+
&lcub;
126+
$json = file_get_contents(__DIR__ . '/path/to/data.json');
127+
$data = json_decode($json, true);
128+
$users = new ArrayList('object');
129+
$users->addRange($data);
130+
131+
$user = $users->where(fn ($user) => $user->Id == $id)->first();
132+
if (!$user) &lcub;
133+
Throw \Exception("page not found!", 404);
134+
}
135+
$user->Username = $from->Username;
136+
$user->Email = $from->Username;
137+
$json = json_encode($users->toArray(), JSON_PRETTY_PRINT);
138+
file_put_contents('path/to/data.json', $json);
139+
return $this->view();
140+
}
141+
}
142+
</code></pre>
143+
<br>
144+
<h3>X-XSRF-TOKEN</h3>
145+
<p>
146+
In modern JavaScript-based applications, the Antiforgery tokens are sent to the server via the AJAX request headers, and usually, the token used in this technique is called <samp>X-XSRF-TOKEN</samp>.
147+
</p>
148+
<p>
149+
In the following example, when the user requests the <samp>"/account/create"</samp> endpoint to create a new account, the server returns an HTML form response with <samp>XSRF-TOKEN</samp> as a cookie, which must be sent back to the server via the AJAX request header when the user submits the form to the <samp>"/account/store"</samp> endpoint, which this one has an Antiforgory filter to check if the request is trusted or not before storing the data.
150+
</p>
151+
<pre><code class="language-php">&lt;?php
152+
153+
use DevNet\System\Linq;
154+
use DevNet\Web\Http\HttpContext;
155+
...
156+
157+
$app->useEndpoint(function($routes) &lcub;
158+
$routes->mapGet("/account/create", (function(HttpContext $context) &lcub;
159+
$token = $context->Antiforgery->getToken();
160+
$context.Response->Cookies->add("XSRF-TOKEN", token);
161+
$html = file_get_contents("path/to/editFrom.phtml");
162+
$context->Response->Body->write($html);
163+
}));
164+
165+
$routes.MapPost("/account/store", (function(HttpContext $context) &lcub;
166+
$form = $context->Resquest->Form;
167+
$user = new User();
168+
$user->Username = $form->getValue('username');
169+
$user->Password = password_hash($form->getValue('password'), PASSWORD_DEFAULT);
170+
171+
$json = file_get_contents('path/to/data.json');
172+
$data = json_decode($json);
173+
174+
$data[] = $user;
175+
$json = json_encode($data, JSON_PRETTY_PRINT);
176+
file_put_contents('path/to/data.json', $json);
177+
$context->Response->setStatusCode(200);
178+
})
179+
// add Antiforgery filter to the endpoint "/account/store".
180+
->addFilter(Antiforgery::class);
181+
});</code></pre>
182+
<p>
183+
Here is a Javascript example that uses the AJAX request to send back the <samp>XSRF-TOKEN</samp> to the server after receiving it from the server via the response cookie.
184+
</p>
185+
<pre><code class="language-javascript">const xsrfToken = document.cookie
186+
.split("; ")
187+
.find(row => row.startsWith("XSRF-TOKEN="))
188+
.split("=")[1];
189+
190+
var request = new XMLHttpRequest();
191+
request.open("POST", "https://example.com/account/edit");
192+
request.setRequestHeader("X-XSRF-TOKEN", xsrfToken);
193+
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
194+
request.send("username=user&[email protected]");
195+
request.onload = function() &lcub;
196+
console.log(this.status);
197+
}
198+
</code></pre>
199+
</article>
200+
<nav class="no-print" aria-label="Page navigation">
201+
<ul class="nav-page">
202+
<li class="nav-page-item">
203+
<a class="nav-page-link" routerLink="/docs/security/authorization">
204+
<i class="chevron left"></i> Previous
205+
</a>
206+
</li>
207+
<li class="nav-page-item">
208+
<a class="nav-page-link" routerLink="/docs/entity/start">
209+
Next <i class="chevron right"></i>
210+
</a>
211+
</li>
212+
</ul>
213+
</nav>
214+
</div>
215+
</div>
216+
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { AntiforgeryComponent } from './antiforgery.component';
4+
5+
describe('AntiforgeryComponent', () => {
6+
let component: AntiforgeryComponent;
7+
let fixture: ComponentFixture<AntiforgeryComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [ AntiforgeryComponent ]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(AntiforgeryComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import hljs from 'highlight.js/lib/common';
3+
4+
@Component({
5+
selector: 'security-antiforgery',
6+
templateUrl: './antiforgery.component.html',
7+
styleUrls: ['./antiforgery.component.css']
8+
})
9+
export class AntiforgeryComponent implements OnInit {
10+
11+
constructor() { }
12+
13+
ngOnInit(): void {
14+
hljs.highlightAll();
15+
}
16+
17+
}

src/app/security/security-routing.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { RouterModule, Routes } from '@angular/router';
33
import { OverviewComponent } from './overview/overview.component';
44
import { AuthenticationComponent } from './authentication/authentication.component';
55
import { AuthorizationComponent } from './authorization/authorization.component';
6+
import { AntiforgeryComponent } from './antiforgery/antiforgery.component';
67

78
const routes: Routes = [
89
{ path: 'docs/security/overview', component: OverviewComponent },
910
{ path: 'docs/security/authentication', component: AuthenticationComponent },
1011
{ path: 'docs/security/authorization', component: AuthorizationComponent },
12+
{ path: 'docs/security/antiforgery', component: AntiforgeryComponent },
1113
{ path: 'docs/security', redirectTo: 'docs/security/overview', pathMatch: 'full' }
1214
];
1315

src/app/security/security.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { SharedModule } from '../shared/shared.module';
55
import { OverviewComponent } from './overview/overview.component';
66
import { AuthenticationComponent } from './authentication/authentication.component';
77
import { AuthorizationComponent } from './authorization/authorization.component';
8+
import { AntiforgeryComponent } from './antiforgery/antiforgery.component';
89

910
@NgModule({
1011
declarations: [
1112
OverviewComponent,
1213
AuthenticationComponent,
13-
AuthorizationComponent
14+
AuthorizationComponent,
15+
AntiforgeryComponent
1416
],
1517
imports: [
1618
CommonModule,

0 commit comments

Comments
 (0)