Skip to content

Commit

Permalink
feat(auth): add OAuth2 example with google auth
Browse files Browse the repository at this point in the history
  • Loading branch information
nnixaa authored Jun 15, 2018
1 parent 7634df5 commit fd95095
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 26 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
205 changes: 205 additions & 0 deletions docs/articles/auth-oauth2.md
Original file line number Diff line number Diff line change
@@ -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.
<hr>

## 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.
<hr>

## 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.
<hr>

## 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 {
}
```
<hr>

## 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,
},
]),
```
<hr>

## 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 {
}
```
<hr>

## 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: `
<button class="btn btn-success" *ngIf="!token" (click)="login()">Sign In with Google</button>
`,
})
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: `
<nb-layout>
<nb-layout-column>Authenticating...</nb-layout-column>
</nb-layout>
`,
})
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.
<hr>

## 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).

<hr>

## Related Articles

- [NbAuthService](/docs/auth/nbauthservice)
- [NbTokenService](/docs/auth/nbtokenservice)
- Receiving [user token after authentication](/docs/auth/getting-user-token)
- [NbOAuth2AuthStrategy](/docs/auth/nboauth2authstrategy)
11 changes: 11 additions & 0 deletions docs/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
16 changes: 12 additions & 4 deletions src/framework/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -111,6 +118,7 @@ export class NbAuthModule {
NbTokenService,
NbDummyAuthStrategy,
NbPasswordAuthStrategy,
NbOAuth2AuthStrategy,
],
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/framework/auth/strategies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
26 changes: 15 additions & 11 deletions src/framework/auth/strategies/oauth2/oauth2-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {

Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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) => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down
Loading

0 comments on commit fd95095

Please sign in to comment.