Skip to content

Commit

Permalink
Merge pull request #61 from piotrkaczmarek/oauth-auth-flow
Browse files Browse the repository at this point in the history
Implements OAuth Sign In newWindow and sameWindow modes
  • Loading branch information
neroniaky authored Nov 27, 2016
2 parents f6a662c + 656ac02 commit cec1235
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 12 deletions.
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ On successful sign in the user will be redirect to `restricted`.
- [`.validateToken()`](#validatetoken)
- [`.updatePassword()`](#updatepassword)
- [`.resetPassword()`](#resetpassword)
- [`.signInOAuth()`](#signinoauth)
- [`.processOAuthCallback()`](#processoauthcallback)
- [HTTP Service Wrapper](#http-service-wrapper)
- [Multiple User Types](#multiple-user-types)
- [Route Guards](#route-guards)
Expand Down Expand Up @@ -128,6 +130,12 @@ constructor(private _tokenService: Angular2TokenService) {
resetPasswordPath: 'auth/password',
resetPasswordCallback: window.location.href,
oAuthPaths: {
github: 'auth/github'
},
oAuthCallbackPath: 'oauth_callback',
oAuthWindowType: 'newWindow',
userTypes: null,
globalOptions: {
Expand Down Expand Up @@ -157,7 +165,9 @@ constructor(private _tokenService: Angular2TokenService) {
| `resetPasswordCallback?: string` | Sets the path user are redirected to after email confirmation for password reset |
| `userTypes?: UserTypes[]` | Allows the configuration of multiple user types (see [Multiple User Types](#multiple-user-types)) |
| `globalOptions?: GlobalOptions` | Allows the configuration of global options (see below) |
| `oAuthPaths?: { [key:string]: string }` | Sets paths for sign in with OAuth |
| `oAuthCallbackPath?: string` | Sets path for OAuth sameWindow callback |
| `oAuthWindowType?:`string` | Window type for Oauth authentication |
### Global Options
| Options | Description |
| ------------------------------------- | ----------------------------------------------- |
Expand Down Expand Up @@ -274,6 +284,56 @@ this._tokenService.updatePassword({
);
```
### .signInOAuth()
Initiates OAuth authentication flow. Currently, it supports two window modes:
`newWindow` (default) and `sameWindow` (settable in config as `oAuthWindowType`).
- When `oAuthWindowType` is set to `newWindow`, `.signInOAuth()` opens a new window and returns an observable.
- When `oAuthWindowType` is set to `sameWindow`, `.signInOAuth()` returns nothing and redirects user to auth provider.
After successful authentication, it redirects back to `oAuthCallbackPath`. Application router needs to intercept
this route and call `processOAuthCallback()` to fetch `AuthData` from params.
`signInOAuth(oAuthType: string)`
#### Example:
```javascript
this._tokenService.signInOAuth(
'github'
).subscribe(
res => console.log(res),
error => console.log(error)
);
```
### .processOAuthCallback()
Fetches AuthData from params sent via OAuth redirection in `sameWindow` flow.
`processOAuthCallback()`
#### Example
Callback route:
```javascript
RouterModule.forRoot([
{ path: 'oauth_callback', component: OauthCallbackComponent }
])
```
Callback component:
```javascript
@Component({
template: ''
})
export class OauthCallbackComponent implements OnInit {
constructor(private _tokenService: Angular2TokenService) {}
ngOnInit() {
this._tokenService.processOAuthCallback();
}
}
```
## HTTP Service Wrapper
`Angular2TokenService` wraps all standard Angular2 Http Service calls for authentication and token processing.
If `apiPath` is configured it gets added in front of path.
Expand Down
8 changes: 3 additions & 5 deletions src/angular2-token.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ export interface UserType {
path: string;
}

export interface OAuthPaths {
github?: string;
}

export interface GlobalOptions {
headers?: { [key:string]: string; }
}
Expand All @@ -82,7 +78,9 @@ export interface Angular2TokenOptions {

userTypes?: UserType[];

oAuthPaths?: OAuthPaths;
oAuthPaths?: { [key:string]: string; };
oAuthCallbackPath?: string;
oAuthWindowType?: string;

globalOptions?: GlobalOptions;
}
84 changes: 78 additions & 6 deletions src/angular2-token.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/operator/pluck';
import 'rxjs/add/operator/filter';

import {
SignInData,
Expand Down Expand Up @@ -107,7 +111,8 @@ export class Angular2TokenService implements CanActivate {
oAuthPaths: {
github: 'auth/github'
},

oAuthCallbackPath: 'oauth_callback',
oAuthWindowType: 'newWindow',
globalOptions: {
headers: {
'Content-Type': 'application/json',
Expand Down Expand Up @@ -166,13 +171,23 @@ export class Angular2TokenService implements CanActivate {

signInOAuth(oAuthType: string) {

let oAuthPath: string;
let oAuthPath: string = this._getOAuthPath(oAuthType);
let callbackUrl: string = `${window.location.origin}/${this._options.oAuthCallbackPath}`;
let oAuthWindowType: string = this._options.oAuthWindowType;
let authUrl: string = this._buildOAuthUrl(oAuthPath, callbackUrl, oAuthWindowType);

if (oAuthType == 'github') {
oAuthPath = this._options.oAuthPaths.github
if (oAuthWindowType == 'newWindow') {
let popup = window.open(authUrl, '_blank', 'closebuttoncaption=Cancel');
return this._requestCredentialsViaPostMessage(popup);
} else if (oAuthWindowType == 'sameWindow') {
window.location.href = authUrl;
} else {
throw `Unsupported oAuthWindowType "${oAuthWindowType}"`;
}
}

window.open(this._constructUserPath() + oAuthPath);
processOAuthCallback() {
this._getAuthDataFromParams();
}

// Sign out request and delete storage
Expand Down Expand Up @@ -385,7 +400,7 @@ export class Angular2TokenService implements CanActivate {
if(this._activatedRoute.queryParams) // Fix for Testing, needs to be removed later
this._activatedRoute.queryParams.subscribe(queryParams => {
let authData: AuthData = {
accessToken: queryParams['token'],
accessToken: queryParams['token'] || queryParams['auth_token'],
client: queryParams['client_id'],
expiry: queryParams['expiry'],
tokenType: 'Bearer',
Expand All @@ -397,6 +412,18 @@ export class Angular2TokenService implements CanActivate {
});
}

private _parseAuthDataFromPostMessage(data: any){
let authData: AuthData = {
accessToken: data['auth_token'],
client: data['client_id'],
expiry: data['expiry'],
tokenType: 'Bearer',
uid: data['uid']
};

this._setAuthData(authData);
}

// Write auth data to storage
private _setAuthData(authData: AuthData) {

Expand Down Expand Up @@ -476,4 +503,49 @@ export class Angular2TokenService implements CanActivate {
else
return this._options.apiPath + '/';
}

private _getOAuthPath(oAuthType: string): string {
let oAuthPath: string;

oAuthPath = this._options.oAuthPaths[oAuthType];
if (oAuthPath == null) {
oAuthPath = `/auth/${oAuthType}`;
}
return oAuthPath;
}

private _buildOAuthUrl(oAuthPath: string, callbackUrl: string, windowType: string): string {
let url: string;

url = `${window.location.origin}/${oAuthPath}`;
url += `?omniauth_window_type=${windowType}`;
url += `&auth_origin_url=${encodeURIComponent(callbackUrl)}`;
if (this._currentUserType != null) {
url += `&resource_class=${this._currentUserType.name}`;
}
return url;
}

private _requestCredentialsViaPostMessage(authWindow: any): Observable<any> {
let poller_observ = Observable.interval(500);
let response_observ = Observable.fromEvent(window, 'message')
.pluck('data')
.filter(this._oauthWindowResponseFilter);

let response_subscription = response_observ.subscribe(this._parseAuthDataFromPostMessage.bind(this));
let poller_subscription = poller_observ.subscribe(() => {
if (authWindow.closed) {
poller_subscription.unsubscribe();
} else {
authWindow.postMessage('requestCredentials', '*');
}
});
return response_observ;
}

private _oauthWindowResponseFilter(data: any) {
if(data.message == 'deliverCredentials' || data.message == 'authFailure') {
return data;
}
}
}

0 comments on commit cec1235

Please sign in to comment.