diff --git a/README.md b/README.md index d316741..fb93edf 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ A simple, configurable, easy-to-start component for handling reCAPTCHA. * [Resizing](#example-resizing) * [SystemJS configuration](#example-systemjs) * [Loading from a different location](#example-different-url) + * [Specifying nonce for Content-Security-Policy](#example-csp-nonce) ## Installation @@ -357,3 +358,20 @@ import { RECAPTCHA_BASE_URL } from 'ng-recaptcha'; ], }) export class MyModule { } ``` + +### Specifying nonce for Content-Security-Policy + +Per [reCAPTCHA FAQ on CSP](https://developers.google.com/recaptcha/docs/faq#im-using-content-security-policy-csp-on-my-website-how-can-i-configure-it-to-work-with-recaptcha), the recommended approach for that is to supply nonce to the script tag. This is possible by providing the `RECAPTCHA_NONCE` token, for example: + +```javascript +import { RECAPTCHA_NONCE } from 'ng-recaptcha'; + +@NgModule({ + providers: [ + { + provide: RECAPTCHA_NONCE, + useValue: '', + }, + ], +}) export class MyModule { } +``` diff --git a/index.ts b/index.ts index 3fbe848..0e4df11 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,9 @@ export { RecaptchaComponent } from './recaptcha/recaptcha.component'; -export { RecaptchaLoaderService, RECAPTCHA_LANGUAGE, RECAPTCHA_BASE_URL } from './recaptcha/recaptcha-loader.service'; +export { + RecaptchaLoaderService, + RECAPTCHA_LANGUAGE, + RECAPTCHA_BASE_URL, + RECAPTCHA_NONCE, +} from './recaptcha/recaptcha-loader.service'; export { RecaptchaModule } from './recaptcha/recaptcha.module'; export { RECAPTCHA_SETTINGS, RecaptchaSettings } from './recaptcha/recaptcha-settings'; diff --git a/recaptcha/recaptcha-loader.service.ts b/recaptcha/recaptcha-loader.service.ts index 73cb064..d793ad7 100644 --- a/recaptcha/recaptcha-loader.service.ts +++ b/recaptcha/recaptcha-loader.service.ts @@ -10,6 +10,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; export const RECAPTCHA_LANGUAGE = new InjectionToken('recaptcha-language'); export const RECAPTCHA_BASE_URL = new InjectionToken('recaptcha-base-url'); +export const RECAPTCHA_NONCE = new InjectionToken('recaptcha-nonce-tag'); @Injectable() export class RecaptchaLoaderService { @@ -25,15 +26,19 @@ export class RecaptchaLoaderService { private language: string; /** @internal */ private baseUrl: string; + /** @internal */ + private nonce: string; constructor( // tslint:disable-next-line:no-any @Inject(PLATFORM_ID) private readonly platformId: any, @Optional() @Inject(RECAPTCHA_LANGUAGE) language?: string, @Optional() @Inject(RECAPTCHA_BASE_URL) baseUrl?: string, + @Optional() @Inject(RECAPTCHA_NONCE) nonce?: string, ) { this.language = language; this.baseUrl = baseUrl; + this.nonce = nonce; this.init(); this.ready = isPlatformBrowser(this.platformId) ? RecaptchaLoaderService.ready.asObservable() : of(); } @@ -53,6 +58,10 @@ export class RecaptchaLoaderService { const langParam = this.language ? '&hl=' + this.language : ''; const baseUrl = this.baseUrl || 'https://www.google.com/recaptcha/api.js'; script.src = `${baseUrl}?render=explicit&onload=ng2recaptchaloaded${langParam}`; + if (this.nonce) { + // tslint:disable-next-line:no-any Remove "any" cast once we upgrade Angular to 7 and TypeScript along with it + (script as any).nonce = this.nonce; + } script.async = true; script.defer = true; document.head.appendChild(script);