diff --git a/README.md b/README.md
index 379ba80fb3..e071dc00c5 100644
--- a/README.md
+++ b/README.md
@@ -26,12 +26,14 @@ Main Nebular module which includes UI Kit and Theme System.
| Sidebar | Layout sidebar with multiple states. |
| Menu | Multi-depth menu component. |
| Card | Basic card with arbitrary header and footer. |
+| Alert | Alert component showing message and statuses with close button. |
| Flip Card | A card with back and front sides and "flip" switching effect. |
| Reveal Card | A card with back and front sides and "reveal" switching effect. |
| Search | Global search with amazing showing animations. |
| Tabs | Basic and route-based tab components. |
| Actions | Horizontal actions bar. |
| User | User avatar with a context menu. |
+| Progress Bar | Component for progress indication. |
| Badge | Simple helper components for showing a badge. |
| Popover | Pop-up box that appears when a user clicks on an element. |
| Context Menu | A directive to attach a menu to any element. |
diff --git a/docs/articles/auth-oauth2.md b/docs/articles/auth-oauth2.md
new file mode 100644
index 0000000000..02b2826cb6
--- /dev/null
+++ b/docs/articles/auth-oauth2.md
@@ -0,0 +1,205 @@
+# Strategy
+
+Using `NbOAuth2AuthStrategy` is becomes possible to configure authentication with a lot of 3rd party authentication providers, such as Google, Facebook, etc.
+There is no need in any backend implementation, as [OAuth2](https://tools.ietf.org/html/rfc6749) protocol enables completely server-less authentication flow as one of the options.
+
+In this article we will setup and configure `NbOAuth2AuthStrategy` for [Google Authentication](https://developers.google.com/identity/protocols/OAuth2UserAgent)
+based on [Implicit](https://tools.ietf.org/html/rfc6749#section-4.2) flow.
+
+
+## Step 1. Obtain keys
+
+As first step we need to setup an application and obtain its keys on the authentication server (Google in our case).
+More details how to do this you can find on [Enable APIs for your project](https://developers.google.com/identity/protocols/OAuth2UserAgent#enable-apis) page.
+We won't copy over this part of the article here, but as a result you should have your `client_id` - unique application identifier.
+
+
+## Step 2. Enable a Strategy
+
+Next step would be to register `NbOAuth2AuthStrategy` in your `app.module.ts`:
+
+```ts
+@NgModule({
+ imports: [
+ // ...
+
+ NbAuthModule.forRoot({
+ strategies: [
+ NbOAuth2AuthStrategy.setup({
+ name: 'google',
+ // ...
+ }),
+ ],
+ }),
+ ],
+})
+export class YourModule {
+}
+```
+
+So we imported `NbAuthModule` and provided a strategy we want to use. If you already have some strategy configuted - don't worry, you can simple append a new one to the `strategies` array.
+We also assigned a `name` - `google`. We will use this alias later on to call the strategy.
+
+
+## Step 3. Configure
+
+Let's fill in our strategy with some settings. We add the `client_id` obtained in Step 1. We don't need client_secret for this authentication flow, so we leave it empty.
+Then we set `authorize` endpoint, response_type (which is `token` in our case) and [scope](https://tools.ietf.org/html/rfc6749#section-3.3) of the authentication:
+
+```ts
+@NgModule({
+ imports: [
+ // ...
+
+ NbAuthModule.forRoot({
+ strategies: [
+ NbOAuth2AuthStrategy.setup({
+ name: 'google',
+ clientId: 'YOUR_CLIENT_ID',
+ clientSecret: '',
+ authorize: {
+ endpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
+ responseType: NbOAuth2ResponseType.TOKEN,
+ scope: 'https://www.googleapis.com/auth/userinfo.profile',
+ },
+ }),
+ ],
+ }),
+ ],
+})
+export class YourModule {
+}
+```
+
+
+## Step 4. Routes
+
+We need at least two routes to be able to organize OAuth2 flow. First one - "login" route, where we can simply have a button to initiate authentication process.
+The second one - so-called "callback" route, we need to handle OAuth2 server response.
+Let's add both to our routing referring some empty components:
+
+```ts
+RouterModule.forChild([
+ {
+ path: '',
+ component: NbOAuth2LoginComponent,
+ },
+ {
+ path: 'callback',
+ component: NbOAuth2CallbackComponent,
+ },
+]),
+```
+
+
+## Step 5. Redirect URI
+
+The last configuration bit is to setup the `redirect_uri` parameter. Make sure you added the url to the Google Console as per the [documentation](https://developers.google.com/identity/protocols/OAuth2UserAgent#redirecting).
+
+Now let's complete the setup:
+```ts
+@NgModule({
+ imports: [
+ // ...
+
+ NbAuthModule.forRoot({
+ strategies: [
+ NbOAuth2AuthStrategy.setup({
+ name: 'google',
+ clientId: 'YOUR_CLIENT_ID',
+ clientSecret: '',
+ authorize: {
+ endpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
+ responseType: NbOAuth2ResponseType.TOKEN,
+ scope: 'https://www.googleapis.com/auth/userinfo.profile',
+
+
+ redirectUri: 'http://localhost:4100/example/oauth2/callback',
+ },
+ }),
+ ],
+ }),
+ ],
+})
+export class YourModule {
+}
+```
+
+
+## Step 6. Complete your components
+
+And finally, let's add some code to our component to initiate the authentication. First - `NbOAuth2LoginComponent`:
+
+
+```ts
+@Component({
+ selector: 'nb-oauth2-login',
+ template: `
+
+ `,
+})
+export class NbOAuth2LoginComponent implements OnDestroy {
+
+ alive = true;
+
+ login() {
+ this.authService.authenticate('google')
+ .pipe(takeWhile(() => this.alive))
+ .subscribe((authResult: NbAuthResult) => {
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.alive = false;
+ }
+}
+```
+The code is pretty much straightforward - we call `NbAuthService`.`authenticate` method and pass our strategy alias - `google` subscribing to result.
+This will prepare an `authorization` request url and redirect us to google authentication server.
+
+Now, we need to configure that "callback" url to be able to properly handle response:
+
+```ts
+@Component({
+ selector: 'nb-playground-oauth2-callback',
+ template: `
+
+ Authenticating...
+
+ `,
+})
+export class NbOAuth2CallbackPlaygroundComponent implements OnDestroy {
+
+ alive = true;
+
+ constructor(private authService: NbAuthService, private router: Router) {
+ this.authService.authenticate('google')
+ .pipe(takeWhile(() => this.alive))
+ .subscribe((authResult: NbAuthResult) => {
+ if (authResult.isSuccess()) {
+ this.router.navigateByUrl('/pages/dashboard');
+ }
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.alive = false;
+ }
+}
+```
+Here we call the same `authenticate` method, which will determines that we are in the `redirect` state, handle the response, save your token and redirect you back to your app.
+
+
+## Complete example
+
+A complete code example could be found on [GitHub](https://github.com/akveo/nebular/tree/master/src/playground/oauth2).
+And here the playground example avaible to play around with [OAuth2 Nebular Example](/example/oauth2).
+
+
+
+## Related Articles
+
+- [NbAuthService](/docs/auth/nbauthservice)
+- [NbTokenService](/docs/auth/nbtokenservice)
+- Receiving [user token after authentication](/docs/auth/getting-user-token)
+- [NbOAuth2AuthStrategy](/docs/auth/nboauth2authstrategy)
diff --git a/docs/structure.ts b/docs/structure.ts
index dcc8f0be1f..6e77578442 100644
--- a/docs/structure.ts
+++ b/docs/structure.ts
@@ -441,6 +441,17 @@ export const structure = [
},
],
},
+ {
+ type: 'page',
+ name: 'Configuring Google OAuth2',
+ children: [
+ {
+ type: 'block',
+ block: 'markdown',
+ source: 'auth-oauth2.md',
+ },
+ ],
+ },
{
type: 'page',
name: 'NbAuthService',
diff --git a/src/framework/auth/auth.module.ts b/src/framework/auth/auth.module.ts
index f2330bfede..a337249ed8 100644
--- a/src/framework/auth/auth.module.ts
+++ b/src/framework/auth/auth.module.ts
@@ -10,19 +10,26 @@ import {
NB_AUTH_FALLBACK_TOKEN,
NbAuthService,
NbAuthSimpleToken,
+ NbAuthTokenClass,
+ NbAuthTokenParceler,
NbTokenLocalStorage,
NbTokenService,
NbTokenStorage,
- NbAuthTokenClass,
- NbAuthTokenParceler,
} from './services';
-import { NbAuthStrategy, NbAuthStrategyOptions, NbDummyAuthStrategy, NbPasswordAuthStrategy } from './strategies';
+import {
+ NbAuthStrategy,
+ NbAuthStrategyOptions,
+ NbDummyAuthStrategy,
+ NbOAuth2AuthStrategy,
+ NbPasswordAuthStrategy,
+} from './strategies';
import {
defaultAuthOptions,
NB_AUTH_INTERCEPTOR_HEADER,
NB_AUTH_OPTIONS,
- NB_AUTH_STRATEGIES, NB_AUTH_TOKENS,
+ NB_AUTH_STRATEGIES,
+ NB_AUTH_TOKENS,
NB_AUTH_USER_OPTIONS,
NbAuthOptions,
NbAuthStrategyClass,
@@ -111,6 +118,7 @@ export class NbAuthModule {
NbTokenService,
NbDummyAuthStrategy,
NbPasswordAuthStrategy,
+ NbOAuth2AuthStrategy,
],
};
}
diff --git a/src/framework/auth/strategies/index.ts b/src/framework/auth/strategies/index.ts
index f77ba11989..d907090f3d 100644
--- a/src/framework/auth/strategies/index.ts
+++ b/src/framework/auth/strategies/index.ts
@@ -4,3 +4,5 @@ export * from './dummy/dummy-strategy';
export * from './dummy/dummy-strategy-options';
export * from './password/password-strategy';
export * from './password/password-strategy-options';
+export * from './oauth2/oauth2-strategy';
+export * from './oauth2/oauth2-strategy.options';
diff --git a/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts b/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts
index 12268a404f..60131df64c 100644
--- a/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts
+++ b/src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts
@@ -9,12 +9,16 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { NB_WINDOW } from '@nebular/theme';
-import { of as observableOf } from 'rxjs';
import { NbOAuth2AuthStrategy } from './oauth2-strategy';
import { NbOAuth2GrantType, NbOAuth2ResponseType } from './oauth2-strategy.options';
import { NbAuthResult, nbAuthCreateToken, NbAuthOAuth2Token } from '../../services';
+function createURL(params: any) {
+ return Object.keys(params).map((k) => {
+ return `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`;
+ }).join('&');
+}
describe('oauth2-auth-strategy', () => {
@@ -44,7 +48,7 @@ describe('oauth2-auth-strategy', () => {
beforeEach(() => {
windowMock = { location: { href: '' } };
- routeMock = { params: observableOf({}), queryParams: observableOf({}) };
+ routeMock = { snapshot: { params: {}, queryParams: {}, fragment: '' } };
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule],
@@ -93,7 +97,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle success redirect and sends correct token request', (done: DoneFn) => {
- routeMock.queryParams = observableOf({code: 'code'});
+ routeMock.snapshot.queryParams = { code: 'code' };
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -117,7 +121,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle error redirect back', (done: DoneFn) => {
- routeMock.queryParams = observableOf(tokenErrorResponse);
+ routeMock.snapshot.queryParams = tokenErrorResponse;
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -134,7 +138,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle error token response', (done: DoneFn) => {
- routeMock.queryParams = observableOf({code: 'code'});
+ routeMock.snapshot.queryParams = {code: 'code'};
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -226,7 +230,7 @@ describe('oauth2-auth-strategy', () => {
it('handle success redirect back with token', (done: DoneFn) => {
const token = { access_token: 'token', token_type: 'bearer' };
- routeMock.params = observableOf(token);
+ routeMock.snapshot.fragment = createURL(token);
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -242,7 +246,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle error redirect back', (done: DoneFn) => {
- routeMock.params = observableOf(tokenErrorResponse);
+ routeMock.snapshot.fragment = createURL(tokenErrorResponse);
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -307,7 +311,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle success redirect and sends correct token request', (done: DoneFn) => {
- routeMock.queryParams = observableOf({code: 'code'});
+ routeMock.snapshot.queryParams = { code: 'code' };
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -331,7 +335,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle success redirect back with token request', (done: DoneFn) => {
- routeMock.queryParams = observableOf({code: 'code'});
+ routeMock.snapshot.queryParams = { code: 'code' };
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -350,7 +354,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle error redirect back', (done: DoneFn) => {
- routeMock.queryParams = observableOf(tokenErrorResponse);
+ routeMock.snapshot.queryParams = tokenErrorResponse;
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
@@ -389,7 +393,7 @@ describe('oauth2-auth-strategy', () => {
});
it('handle error token response', (done: DoneFn) => {
- routeMock.queryParams = observableOf({code: 'code'});
+ routeMock.snapshot.queryParams = { code: 'code' };
strategy.authenticate()
.subscribe((result: NbAuthResult) => {
diff --git a/src/framework/auth/strategies/oauth2/oauth2-strategy.ts b/src/framework/auth/strategies/oauth2/oauth2-strategy.ts
index f350eaee93..cbf5829c3b 100644
--- a/src/framework/auth/strategies/oauth2/oauth2-strategy.ts
+++ b/src/framework/auth/strategies/oauth2/oauth2-strategy.ts
@@ -13,23 +13,20 @@ import { NB_WINDOW } from '@nebular/theme';
import { NbAuthStrategy } from '../auth-strategy';
import { NbAuthRefreshableToken, NbAuthResult } from '../../services/';
import { NbOAuth2AuthStrategyOptions, NbOAuth2ResponseType, auth2StrategyOptions } from './oauth2-strategy.options';
+import { NbAuthStrategyClass } from '../../auth.options';
/**
* OAuth2 authentication strategy.
*
- * @example
- *
* Strategy settings:
*
- * ```
- *
+ * ```ts
* export enum NbOAuth2ResponseType {
* CODE = 'code',
* TOKEN = 'token',
* }
*
- * // TODO: password, client_credentials
* export enum NbOAuth2GrantType {
* AUTHORIZATION_CODE = 'authorization_code',
* REFRESH_TOKEN = 'refresh_token',
@@ -82,13 +79,17 @@ import { NbOAuth2AuthStrategyOptions, NbOAuth2ResponseType, auth2StrategyOptions
@Injectable()
export class NbOAuth2AuthStrategy extends NbAuthStrategy {
+ static setup(options: NbOAuth2AuthStrategyOptions): [NbAuthStrategyClass, NbOAuth2AuthStrategyOptions] {
+ return [NbOAuth2AuthStrategy, options];
+ }
+
get responseType() {
return this.getOption('authorize.responseType');
}
protected redirectResultHandlers = {
[NbOAuth2ResponseType.CODE]: () => {
- return this.route.queryParams.pipe(
+ return observableOf(this.route.snapshot.queryParams).pipe(
switchMap((params: any) => {
if (params.code) {
return this.requestToken(params.code)
@@ -106,7 +107,8 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
);
},
[NbOAuth2ResponseType.TOKEN]: () => {
- return this.route.params.pipe(
+ return observableOf(this.route.snapshot.fragment).pipe(
+ map(fragment => this.parseHashAsQueryParams(fragment)),
map((params: any) => {
if (!params.error) {
return new NbAuthResult(
@@ -132,12 +134,13 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
protected redirectResults = {
[NbOAuth2ResponseType.CODE]: () => {
- return this.route.queryParams.pipe(
+ return observableOf(this.route.snapshot.queryParams).pipe(
map((params: any) => !!(params && (params.code || params.error))),
);
},
[NbOAuth2ResponseType.TOKEN]: () => {
- return this.route.params.pipe(
+ return observableOf(this.route.snapshot.fragment).pipe(
+ map(fragment => this.parseHashAsQueryParams(fragment)),
map((params: any) => !!(params && (params.access_token || params.error))),
);
},
@@ -157,7 +160,7 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
switchMap((result: boolean) => {
if (!result) {
this.authorizeRedirect();
- return observableOf(null);
+ return observableOf(new NbAuthResult(true));
}
return this.getAuthorizationResult();
}),
@@ -297,6 +300,14 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
return `${endpoint}?${query}`;
}
+ protected parseHashAsQueryParams(hash: string): { [key: string]: string } {
+ return hash ? hash.split('&').reduce((acc: any, part: string) => {
+ const item = part.split('=');
+ acc[item[0]] = decodeURIComponent(item[1]);
+ return acc;
+ }, {}) : {};
+ }
+
register(data?: any): Observable {
throw new Error('`register` is not supported by `NbOAuth2AuthStrategy`, use `authenticate`.');
}
@@ -310,6 +321,6 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
}
logout(): Observable {
- throw new Error('`logout` is not supported by `NbOAuth2AuthStrategy`, use `authenticate`.');
+ return observableOf(new NbAuthResult(true));
}
}
diff --git a/src/playground/oauth2/oauth2-callback.component.ts b/src/playground/oauth2/oauth2-callback.component.ts
new file mode 100644
index 0000000000..99c88c3d99
--- /dev/null
+++ b/src/playground/oauth2/oauth2-callback.component.ts
@@ -0,0 +1,37 @@
+/**
+ * @license
+ * Copyright Akveo. All Rights Reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+
+import { Component, OnDestroy } from '@angular/core';
+import { NbAuthResult, NbAuthService } from '@nebular/auth';
+import { Router } from '@angular/router';
+import { takeWhile } from 'rxjs/operators';
+
+@Component({
+ selector: 'nb-playground-oauth2-callback',
+ template: `
+
+ Authenticating...
+
+ `,
+})
+export class NbOAuth2CallbackComponent implements OnDestroy {
+
+ alive = true;
+
+ constructor(private authService: NbAuthService, private router: Router) {
+ this.authService.authenticate('google')
+ .pipe(takeWhile(() => this.alive))
+ .subscribe((authResult: NbAuthResult) => {
+ if (authResult.isSuccess() && authResult.getRedirect()) {
+ this.router.navigateByUrl(authResult.getRedirect());
+ }
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.alive = false;
+ }
+}
diff --git a/src/playground/oauth2/oauth2-login.component.ts b/src/playground/oauth2/oauth2-login.component.ts
new file mode 100644
index 0000000000..dd52d1c537
--- /dev/null
+++ b/src/playground/oauth2/oauth2-login.component.ts
@@ -0,0 +1,63 @@
+/**
+ * @license
+ * Copyright Akveo. All Rights Reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ */
+
+import { Component, OnDestroy } from '@angular/core';
+import { NbAuthOAuth2Token, NbAuthResult, NbAuthService } from '@nebular/auth';
+import { takeWhile } from 'rxjs/operators';
+
+@Component({
+ selector: 'nb-playground-auth',
+ template: `
+
+
+
+
+