forked from aurelia/framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
doc(new): security and polymer docs added
- Loading branch information
1 parent
4364d89
commit 058b767
Showing
3 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
--- | ||
{ | ||
"name": "Integrating with Polymer", | ||
"culture": "en-US", | ||
"description": "Polymer is a library for creating reusable web components declaratively with extra features like databinding and property observation. In many ways, it is similar to Aurelia's component support. However, Polymer also includes an extensive catalog of custom elements for everything from material design to credit card forms to embedding Google services like Google Maps and YouTube. With a bit of work, these components can be incorporated into Aurelia applications as well.", | ||
"engines" : { "aurelia-doc" : "^1.0.0" }, | ||
"author": { | ||
"name": "Ben Navetta", | ||
"url": "https://www.linkedin.com/in/benjaminnavetta" | ||
}, | ||
"contributors": [], | ||
"translators": [], | ||
"keywords": ["Integration", "Web Components", "Polymer"] | ||
} | ||
--- | ||
## [Setup](aurelia-doc://section/1/version/1.0.0) | ||
|
||
The first step is obtaining Polymer, which is generally done with the [Bower](http://bower.io/) package manager. The following `bower.json` will install Polymer's base and material design elements. | ||
|
||
<code-listing heading="Bower Config"> | ||
<source-code lang="JSON"> | ||
{ | ||
"name": "my-aurelia-polymer-project", | ||
"private": true, | ||
"dependencies": { | ||
"polymer": "Polymer/polymer#^1.2.0", | ||
"paper-elements": "PolymerElements/paper-elements#^1.0.6", | ||
"iron-elements": "PolymerElements/iron-elements#^1.0.4", | ||
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.20" | ||
} | ||
} | ||
</source-code> | ||
</code-listing> | ||
|
||
Aurelia must also be configured to use the HTML Imports template loader and the `aurelia-polymer` plugin, both of which can be installed with JSPM. | ||
|
||
<code-listing heading="Plugin Installation"> | ||
<source-code lang="Shell"> | ||
$ jspm install aurelia-html-import-template-loader | ||
$ jspm install 'aurelia-polymer@^1.0.0-beta' | ||
</source-code> | ||
</code-listing> | ||
|
||
In `index.html`, Polymer and the webcomponents.js polyfills need to be loaded before Aurelia is, so that the `aurelia-polymer` plugin can hook into Polymer's element registration system. | ||
|
||
<code-listing heading="Loading Web Components and Polymer"> | ||
<source-code lang="HTML"> | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<!-- ... --> | ||
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script> | ||
<link rel="import" href="bower_components/polymer/polymer.html"> | ||
</head> | ||
<!-- ... --> | ||
</html> | ||
</source-code> | ||
</code-listing> | ||
|
||
It's also a good idea to wait until the web components polyfill has loaded Polymer to bootstrap Aurelia. Instead of directly importing `aurelia-bootstrapper`, wait until the `WebComponentsReady` event fires in `index.html`. | ||
|
||
<code-listing heading="Bootstrapping Aurelia"> | ||
<source-code lang="HTML"> | ||
<!DOCTYPE html> | ||
<script> | ||
document.addEventListener('WebComponentsReady', function() { | ||
System.import('aurelia-bootstrapper'); | ||
}); | ||
</script> | ||
</source-code> | ||
</code-listing> | ||
|
||
In `main.js`, the two Aurelia plugins installed before need to be loaded as well. | ||
|
||
<code-listing heading="Configuring Aurelia"> | ||
<source-code lang="ES 2015/2016"> | ||
export function configure(aurelia) { | ||
aurelia.use | ||
.standardConfiguration() | ||
.developmentLogging(); | ||
|
||
aurelia.use.plugin('aurelia-polymer'); | ||
aurelia.use.plugin('aurelia-html-import-template-loader'); | ||
|
||
aurelia.start().then(a => a.setRoot()); | ||
} | ||
</source-code> | ||
<source-code lang="TypeScript"> | ||
import {Aurelia} from 'aurelia-framework'; | ||
|
||
export function configure(aurelia: Aurelia): void { | ||
aurelia.use | ||
.standardConfiguration() | ||
.developmentLogging(); | ||
|
||
aurelia.use.plugin('aurelia-polymer'); | ||
aurelia.use.plugin('aurelia-html-import-template-loader'); | ||
|
||
aurelia.start().then(a => a.setRoot()); | ||
} | ||
</source-code> | ||
</code-listing> | ||
|
||
At this point, Aurelia and Polymer are ready to go. The examples below incorporate various Polymer elements into the Aurelia skeleton navigation starter. | ||
|
||
## [Importing Elements](aurelia-doc://section/2/version/1.0.0) | ||
|
||
With the normal Aurelia template loader, nothing is allowed outside the root `<template>` element. When using HTML imports, however, the import statements must be before the `<template>`. | ||
|
||
<code-listing heading="Using HTML Imports"> | ||
<source-code lang="HTML"> | ||
<link rel="import" href="/bower_components/paper-drawer-panel/paper-drawer-panel.html"> | ||
<link rel="import" href="/bower_components/paper-toolbar/paper-toolbar.html"> | ||
<link rel="import" href="/bower_components/paper-menu/paper-menu.html"> | ||
<link rel="import" href="/bower_components/paper-item/paper-item.html"> | ||
<link rel="import" href="/bower_components/paper-scroll-header-panel/paper-scroll-header-panel.html"> | ||
<link rel="import" href="/bower_components/paper-icon-button/paper-icon-button.html"> | ||
<link rel="import" href="/bower_components/iron-icons/iron-icons.html"> | ||
|
||
<template> | ||
<!-- ... --> | ||
</template> | ||
</source-code> | ||
</code-listing> | ||
|
||
## [Data Binding](aurelia-doc://section/3/version/1.0.0) | ||
|
||
Given the imports above, this shows how to implement a basic layout in `app.html` with Polymer components. | ||
|
||
<code-listing heading="Using Polymer Elements"> | ||
<source-code lang="HTML"> | ||
<template> | ||
<paper-drawer-panel force-narrow> | ||
<div drawer> | ||
<paper-toolbar class="paper-header"> | ||
<span>Menu</span> | ||
</paper-toolbar> | ||
<paper-menu> | ||
<paper-item repeat.for="row of router.navigation" active.bind="row.isActive"> | ||
<a href.bind="row.href">${row.title}</a> | ||
</paper-item> | ||
</paper-menu> | ||
</div> | ||
<div main> | ||
<paper-toolbar class="paper-header"> | ||
<paper-icon-button icon="menu" tabindex="1" paper-drawer-toggle></paper-icon-button> | ||
<div class="title">${router.title}</div> | ||
<span if.bind="router.isNavigating"><iron-icon icon="autorenew"/></span> | ||
</paper-toolbar> | ||
<div> | ||
<router-view></router-view> | ||
</div> | ||
</div> | ||
</paper-drawer-panel> | ||
</template> | ||
</source-code> | ||
</code-listing> | ||
|
||
Notice that Poylmer elements such as `<paper-drawer-panel>` or `<paper-menu>`, once imported, can be used just like | ||
normal HTML elements. | ||
|
||
All of the standard Aurelia binding syntax continues to work as well. For example, | ||
`repeat.for` is used to populate menu items from the Aurelia router. The `active.bind` | ||
binding on the `<paper-item>` elements in the navigation menu shows that attributes | ||
defined by Polymer are also supported by Aurelia. | ||
|
||
## [Forms and Two-Way Binding](aurelia-doc://section/4/version/1.0.0) | ||
|
||
The updated `welcome.html` uses Polymer input elements to enhance its form. | ||
|
||
<code-listing heading="Using Polymer in a Form"> | ||
<source-code lang="HTML"> | ||
<link rel="import" href="../bower_components/paper-input/paper-input.html"> | ||
<link rel="import" href="../bower_components/iron-form/iron-form.html"> | ||
|
||
<template> | ||
<section class="au-animate"> | ||
<h2>${heading}</h2> | ||
<form is="iron-form" role="form" submit.delegate="submit()"> | ||
<div class="form-group"> | ||
<label for="fn">First Name</label> | ||
<paper-input id="fn" value.two-way="firstName" placeholder="first name"></paper-input> | ||
</div> | ||
<div class="form-group"> | ||
<label for="ln">Last Name</label> | ||
<paper-input id="fn" value.two-way="lastName" placeholder="last name"></paper-input> | ||
</div> | ||
<div class="form-group"> | ||
<label>Full Name</label> | ||
<p class="help-block">${fullName | upper}</p> | ||
</div> | ||
<button type="submit" class="btn btn-default">Submit</button> | ||
</form> | ||
</section> | ||
</template> | ||
</source-code> | ||
</code-listing> | ||
|
||
While not strictly necessary in this case, making the form an `<iron-form>` ensures | ||
that Polymer input elements are submitted along with native HTML elements. | ||
|
||
Note that the `<paper-input>` elements have `.two-way` bindings, not just `.bind`. | ||
With the exception of native HTML form elements, Aurelia defaults to one-way bindings, | ||
so two-way databinding must be specified explicitly. | ||
|
||
A normal `<button>` element is used with Aurelia's `submit.delegate` binding, since | ||
Polymer elements cannot submit forms. | ||
|
||
If form submission semantics were not needed, another option would be to | ||
use a `click.delegate` on a Polymer button instead. | ||
|
||
<code-listing heading="Polymer Element with Event Delegation"> | ||
<source-code lang="HTML"> | ||
<paper-button raised click.delegate="submit()">Submit</paper-button> | ||
</source-code> | ||
</code-listing> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
--- | ||
{ | ||
"name": "Securing Your App", | ||
"culture": "en-US", | ||
"description": "It's important to secure your application. This article will address a couple of simple things you can do to improve the security of your application.", | ||
"engines" : { "aurelia-doc" : "^1.0.0" }, | ||
"author": { | ||
"name": "Rob Eisenberg", | ||
"url": "http://robeisenberg.com" | ||
}, | ||
"contributors": [], | ||
"translators": [], | ||
"keywords": ["Security", "SPA"] | ||
} | ||
--- | ||
## [Introduction](aurelia-doc://section/1/version/1.0.0) | ||
|
||
The first rule of securing client-side applications: The client cannot be trusted. Your backend should not trust the input coming from the front-end, under any circumstance. Malicious individuals often know how to use browser debug tools and manually craft HTTP requests to your backend. You may even find yourself in a situation where a disgruntled employee (or former employee), who is a developer with intimate knowledge of the system, is seeking revenge by attempting a malicious attack. | ||
|
||
**Your primary mechanism for securing any SPA application, Aurelia or otherwise, is to work hard on securing your backend services.** | ||
|
||
> Danger: Security Advice | ||
> This article, more or less, contains only a few quick warnings. It is in no way exhaustive, nor should it be your only resource on securing your application. The bulk of the work in security relates to your server-side technology. You should spend adequate time reading up on and understanding security best practices for whatever backend tech you have chosen. | ||
## [Authentication and Authorization](aurelia-doc://section/2/version/1.0.0) | ||
|
||
When designing your application, consider which backend API calls can be made anonymously, which require a logged-in user and which roles or permissions are required for various authenticated requests. Ensure that your entire API surface area is explicitly covered in this way. Your front-end can facilitate the login process, but ultimately this is a backend task. Here are a few related recommendations: | ||
|
||
* Make sure your server is configured to transmit sensitive resources over HTTPS. You may want to transmit all resources this way. It is more server-intensive, but it will be more secure. You must decide what is appropriate for your application. | ||
* Don't transmit passwords in plain text. | ||
* There are various ways to accomplish CORS. Prefer to use a technique based on server-supported CORS, rather than client-side hacks. | ||
* Control cross-domain requests to your services. Either disallow them or configure your server based on a strict whitelist of allowed domains. | ||
* Require strong passwords | ||
* Never, ever store passwords in plain text. | ||
* Do not allow an endless number of failed login attempts to the same account. | ||
* Consider outsourcing your auth requirements to a cloud provider with greater expertise. | ||
|
||
You can improve the user-experience by plugging into Aurelia's router pipeline with your security specifics. Again, remember, this doesn't secure your app, but only provides a smooth user experience. The real security is on the backend. Here's a quick example of how you might use Aurelia's router to disallow client-side routes based on user role: | ||
|
||
<code-listing heading="Customizing the Navigation Pipeline with Authorization"> | ||
<source-code lang="ES 2015/2016"> | ||
import {Redirect} from 'aurelia-router'; | ||
|
||
export class App { | ||
configureRouter(config) { | ||
config.title = 'Aurelia'; | ||
config.addPipelineStep('authorize', AuthorizeStep); | ||
config.map([ | ||
{ route: ['welcome'], moduleId: 'welcome', title: 'Welcome', settings: { roles: [] } }, | ||
{ route: 'admin', moduleId: 'admin', title: 'Admin' settings: { roles: ['admin'] } } | ||
]); | ||
} | ||
} | ||
|
||
class AuthorizeStep { | ||
run(navigationInstruction, next) { | ||
if (navigationInstruction.getAllInstructions().some(i => i.config.settings.roles.indexOf('admin') !== -1)) { | ||
var isAdmin = /* insert magic here */false; | ||
if (!isAdmin) { | ||
return next.cancel(new Redirect('welcome')); | ||
} | ||
} | ||
|
||
return next(); | ||
} | ||
} | ||
</source-code> | ||
<source-code lang="TypeScript"> | ||
import {Redirect, NavigationInstruction, RouterConfiguration} from 'aurelia-router'; | ||
|
||
export class App { | ||
configureRouter(config: RouterConfiguration): void { | ||
config.title = 'Aurelia'; | ||
config.addPipelineStep('authorize', AuthorizeStep); | ||
config.map([ | ||
{ route: ['welcome'], moduleId: 'welcome', title: 'Welcome', settings: { roles: [] } }, | ||
{ route: 'admin', moduleId: 'admin', title: 'Admin' settings: { roles: ['admin'] } } | ||
]); | ||
} | ||
} | ||
|
||
class AuthorizeStep { | ||
run(navigationInstruction: NavigationInstruction, next: Function): Promise<any> { | ||
if (navigationInstruction.getAllInstructions().some(i => i.config.settings.roles.indexOf('admin') !== -1)) { | ||
var isAdmin = /* insert magic here */false; | ||
if (!isAdmin) { | ||
return next.cancel(new Redirect('welcome')); | ||
} | ||
} | ||
|
||
return next(); | ||
} | ||
} | ||
</source-code> | ||
</code-listing> | ||
|
||
> Info: Route Settings | ||
> Developers can add a `settings` property to any route configuration object and use it to store any data they wish to associate with the route. The value of the `settings` property will be preserved by Aurelia's router and also copied to the navigation model. | ||
## [Validation and Sanitization](aurelia-doc://section/3/version/1.0.0) | ||
|
||
The backend should always perform validation and sanitization of data. Do not rely on your client-side validation and sanitization code. In reality, your client-side validation/santization code should not be seen as anything more than a User Experience enhancement, designed to aid honest users. It will have no affect on anyone who is malicious. | ||
|
||
Here's a few things you should do though: | ||
|
||
* Use client-side validation. This will make your users happy. | ||
* Avoid data-binding to `innerHTML`. If you do, be sure to use a value converter to sanitize the input from the user. | ||
* Be extra careful anytime you are dynamically creating and compiling client-side templates based on user input. | ||
* Be extra careful anytime you are dynamically creating templates on the server based on user input, which will later be processed by Aurelia on the client. | ||
|
||
> Info: We Are Trying To Help You | ||
> Internally, Aurelia makes no use of `eval` or the `Function` constructor. Additionally, all binding expressions are parsed by our strict parser which does not make globals like `window` or `document` available in binding expressions. We've done this to help prevent some common abuses. | ||
## [Secret Data](aurelia-doc://section/4/version/1.0.0) | ||
|
||
Do not embed private keys into your JavaScript code. While the average user may not be able to access them, anyone with true ill intent can simply download your client code, un-minifiy it and use basic regular expressions on the codebase to find things that *look like* sensitive data. Perhaps they've discovered what backend technology you are using or what cloud services your product is based on simply by studying your app's HTTP requests or looking at the page source. Using that information they may be able to refine their search based on certain patterns well-known to users of those technologies, making it easier to find your private keys. | ||
|
||
If you have a need to acquire any secret data on the client, it should be done with great care. Here is a (non-exhaustive) list of recommendations: | ||
|
||
* Always use HTTPS to transmit this information. | ||
* Restrict which users and roles can acquire this information to an absolute minimum. | ||
* Always use time-outs on any secret keys so that, at most, if an attacker gains access, they can't use them for long. | ||
* Be careful how you store these values in memory. Do not store these as class property values or on any object that is linked to the DOM through data-binding or otherwise. Doing so would allow an attacker to gain access to the information through the debug console. If you must store this information, keep it inside a private (non-exported) module-level variable. | ||
* If you need to store this information anywhere, encrypt it first. | ||
|
||
## [Deployment](aurelia-doc://section/5/version/1.0.0) | ||
|
||
When deploying your apps, there are a few things you can do to make it more difficult for attackers to figure out how your client works: | ||
|
||
* Bundle your application and minify it. This is the most basic obfuscation you can do. | ||
* Do not deploy the original client-side source files. Only deploy your bundled, minified app. | ||
* For additional security or IP protection, you may want to look into products such as [jscrambler](https://jscrambler.com/en/). | ||
|
||
## [Prepare for the Inevitable](aurelia-doc://section/6/version/1.0.0) | ||
|
||
Even with the most skilled, security-proficient development team, your app will never be 100% protected. This is a fundamental assumption that you should have from the beginning. Expect to be attacked and expect someone to succeed at some point in time. What will you do if this happens? How will you respond? Will you be able to track down the culprit? Will you be able to identify the issue and quickly seal up the breach? You need a plan. | ||
|
||
Again, most of this come down to server-side implementation. Here are a few basic ideas: | ||
|
||
* Configure server-side logging and make sure it will provide you with useful information. Such information can be very helpful in tracking down how an attack was performed. Make sure you have tools available to you to quickly search through your logs. | ||
* Make sure that all logins are logged. If you are using an auth-token scheme, make sure that all requests log this information. | ||
* Never log sensitive data. | ||
* Consider timing out logins or auth tokens. You can provide refresh mechanisms in order to help the user experience. | ||
* Configure server insight tooling so that threats can be detected earlier. | ||
|
||
## [Do Not Be Nice to Bad Guys](aurelia-doc://section/7/version/1.0.0) | ||
|
||
Be careful what information you give out, especially when something goes wrong. For example, if there's a failed login attempt, do not tell the user whether they got their username or password incorrect. That's too much information. Just tell them they had an incorrect login. Furthermore, be careful with what error information you send to end-users. You should keep detailed, internal error logs, but most of that information should not be sent to the end user. Too much information can help an attacker take a step closer to causing real damage. | ||
|
||
Beyond this, you are under no obligation to provide nice messages of any kind when you know a user is doing something malicious. Just let the application crash. It's fitting for them. |
Oops, something went wrong.