Skip to content

Commit cec1235

Browse files
authored
Merge pull request #61 from piotrkaczmarek/oauth-auth-flow
Implements OAuth Sign In newWindow and sameWindow modes
2 parents f6a662c + 656ac02 commit cec1235

File tree

3 files changed

+142
-12
lines changed

3 files changed

+142
-12
lines changed

README.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ On successful sign in the user will be redirect to `restricted`.
8888
- [`.validateToken()`](#validatetoken)
8989
- [`.updatePassword()`](#updatepassword)
9090
- [`.resetPassword()`](#resetpassword)
91+
- [`.signInOAuth()`](#signinoauth)
92+
- [`.processOAuthCallback()`](#processoauthcallback)
9193
- [HTTP Service Wrapper](#http-service-wrapper)
9294
- [Multiple User Types](#multiple-user-types)
9395
- [Route Guards](#route-guards)
@@ -128,6 +130,12 @@ constructor(private _tokenService: Angular2TokenService) {
128130
resetPasswordPath: 'auth/password',
129131
resetPasswordCallback: window.location.href,
130132
133+
oAuthPaths: {
134+
github: 'auth/github'
135+
},
136+
oAuthCallbackPath: 'oauth_callback',
137+
oAuthWindowType: 'newWindow',
138+
131139
userTypes: null,
132140
133141
globalOptions: {
@@ -157,7 +165,9 @@ constructor(private _tokenService: Angular2TokenService) {
157165
| `resetPasswordCallback?: string` | Sets the path user are redirected to after email confirmation for password reset |
158166
| `userTypes?: UserTypes[]` | Allows the configuration of multiple user types (see [Multiple User Types](#multiple-user-types)) |
159167
| `globalOptions?: GlobalOptions` | Allows the configuration of global options (see below) |
160-
168+
| `oAuthPaths?: { [key:string]: string }` | Sets paths for sign in with OAuth |
169+
| `oAuthCallbackPath?: string` | Sets path for OAuth sameWindow callback |
170+
| `oAuthWindowType?:`string` | Window type for Oauth authentication |
161171
### Global Options
162172
| Options | Description |
163173
| ------------------------------------- | ----------------------------------------------- |
@@ -274,6 +284,56 @@ this._tokenService.updatePassword({
274284
);
275285
```
276286
287+
### .signInOAuth()
288+
Initiates OAuth authentication flow. Currently, it supports two window modes:
289+
`newWindow` (default) and `sameWindow` (settable in config as `oAuthWindowType`).
290+
- When `oAuthWindowType` is set to `newWindow`, `.signInOAuth()` opens a new window and returns an observable.
291+
292+
- When `oAuthWindowType` is set to `sameWindow`, `.signInOAuth()` returns nothing and redirects user to auth provider.
293+
After successful authentication, it redirects back to `oAuthCallbackPath`. Application router needs to intercept
294+
this route and call `processOAuthCallback()` to fetch `AuthData` from params.
295+
296+
`signInOAuth(oAuthType: string)`
297+
298+
#### Example:
299+
300+
```javascript
301+
this._tokenService.signInOAuth(
302+
'github'
303+
).subscribe(
304+
res => console.log(res),
305+
error => console.log(error)
306+
);
307+
```
308+
309+
### .processOAuthCallback()
310+
Fetches AuthData from params sent via OAuth redirection in `sameWindow` flow.
311+
312+
`processOAuthCallback()`
313+
314+
#### Example
315+
316+
Callback route:
317+
```javascript
318+
RouterModule.forRoot([
319+
{ path: 'oauth_callback', component: OauthCallbackComponent }
320+
])
321+
```
322+
323+
Callback component:
324+
```javascript
325+
@Component({
326+
template: ''
327+
})
328+
export class OauthCallbackComponent implements OnInit {
329+
constructor(private _tokenService: Angular2TokenService) {}
330+
331+
ngOnInit() {
332+
this._tokenService.processOAuthCallback();
333+
}
334+
}
335+
```
336+
277337
## HTTP Service Wrapper
278338
`Angular2TokenService` wraps all standard Angular2 Http Service calls for authentication and token processing.
279339
If `apiPath` is configured it gets added in front of path.

src/angular2-token.model.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ export interface UserType {
5252
path: string;
5353
}
5454

55-
export interface OAuthPaths {
56-
github?: string;
57-
}
58-
5955
export interface GlobalOptions {
6056
headers?: { [key:string]: string; }
6157
}
@@ -82,7 +78,9 @@ export interface Angular2TokenOptions {
8278

8379
userTypes?: UserType[];
8480

85-
oAuthPaths?: OAuthPaths;
81+
oAuthPaths?: { [key:string]: string; };
82+
oAuthCallbackPath?: string;
83+
oAuthWindowType?: string;
8684

8785
globalOptions?: GlobalOptions;
8886
}

src/angular2-token.service.ts

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import {
1212
import { ActivatedRoute, Router } from '@angular/router';
1313
import { Observable } from 'rxjs/Observable';
1414
import 'rxjs/add/operator/share';
15+
import 'rxjs/add/observable/interval';
16+
import 'rxjs/add/observable/fromEvent';
17+
import 'rxjs/add/operator/pluck';
18+
import 'rxjs/add/operator/filter';
1519

1620
import {
1721
SignInData,
@@ -107,7 +111,8 @@ export class Angular2TokenService implements CanActivate {
107111
oAuthPaths: {
108112
github: 'auth/github'
109113
},
110-
114+
oAuthCallbackPath: 'oauth_callback',
115+
oAuthWindowType: 'newWindow',
111116
globalOptions: {
112117
headers: {
113118
'Content-Type': 'application/json',
@@ -166,13 +171,23 @@ export class Angular2TokenService implements CanActivate {
166171

167172
signInOAuth(oAuthType: string) {
168173

169-
let oAuthPath: string;
174+
let oAuthPath: string = this._getOAuthPath(oAuthType);
175+
let callbackUrl: string = `${window.location.origin}/${this._options.oAuthCallbackPath}`;
176+
let oAuthWindowType: string = this._options.oAuthWindowType;
177+
let authUrl: string = this._buildOAuthUrl(oAuthPath, callbackUrl, oAuthWindowType);
170178

171-
if (oAuthType == 'github') {
172-
oAuthPath = this._options.oAuthPaths.github
179+
if (oAuthWindowType == 'newWindow') {
180+
let popup = window.open(authUrl, '_blank', 'closebuttoncaption=Cancel');
181+
return this._requestCredentialsViaPostMessage(popup);
182+
} else if (oAuthWindowType == 'sameWindow') {
183+
window.location.href = authUrl;
184+
} else {
185+
throw `Unsupported oAuthWindowType "${oAuthWindowType}"`;
173186
}
187+
}
174188

175-
window.open(this._constructUserPath() + oAuthPath);
189+
processOAuthCallback() {
190+
this._getAuthDataFromParams();
176191
}
177192

178193
// Sign out request and delete storage
@@ -385,7 +400,7 @@ export class Angular2TokenService implements CanActivate {
385400
if(this._activatedRoute.queryParams) // Fix for Testing, needs to be removed later
386401
this._activatedRoute.queryParams.subscribe(queryParams => {
387402
let authData: AuthData = {
388-
accessToken: queryParams['token'],
403+
accessToken: queryParams['token'] || queryParams['auth_token'],
389404
client: queryParams['client_id'],
390405
expiry: queryParams['expiry'],
391406
tokenType: 'Bearer',
@@ -397,6 +412,18 @@ export class Angular2TokenService implements CanActivate {
397412
});
398413
}
399414

415+
private _parseAuthDataFromPostMessage(data: any){
416+
let authData: AuthData = {
417+
accessToken: data['auth_token'],
418+
client: data['client_id'],
419+
expiry: data['expiry'],
420+
tokenType: 'Bearer',
421+
uid: data['uid']
422+
};
423+
424+
this._setAuthData(authData);
425+
}
426+
400427
// Write auth data to storage
401428
private _setAuthData(authData: AuthData) {
402429

@@ -476,4 +503,49 @@ export class Angular2TokenService implements CanActivate {
476503
else
477504
return this._options.apiPath + '/';
478505
}
506+
507+
private _getOAuthPath(oAuthType: string): string {
508+
let oAuthPath: string;
509+
510+
oAuthPath = this._options.oAuthPaths[oAuthType];
511+
if (oAuthPath == null) {
512+
oAuthPath = `/auth/${oAuthType}`;
513+
}
514+
return oAuthPath;
515+
}
516+
517+
private _buildOAuthUrl(oAuthPath: string, callbackUrl: string, windowType: string): string {
518+
let url: string;
519+
520+
url = `${window.location.origin}/${oAuthPath}`;
521+
url += `?omniauth_window_type=${windowType}`;
522+
url += `&auth_origin_url=${encodeURIComponent(callbackUrl)}`;
523+
if (this._currentUserType != null) {
524+
url += `&resource_class=${this._currentUserType.name}`;
525+
}
526+
return url;
527+
}
528+
529+
private _requestCredentialsViaPostMessage(authWindow: any): Observable<any> {
530+
let poller_observ = Observable.interval(500);
531+
let response_observ = Observable.fromEvent(window, 'message')
532+
.pluck('data')
533+
.filter(this._oauthWindowResponseFilter);
534+
535+
let response_subscription = response_observ.subscribe(this._parseAuthDataFromPostMessage.bind(this));
536+
let poller_subscription = poller_observ.subscribe(() => {
537+
if (authWindow.closed) {
538+
poller_subscription.unsubscribe();
539+
} else {
540+
authWindow.postMessage('requestCredentials', '*');
541+
}
542+
});
543+
return response_observ;
544+
}
545+
546+
private _oauthWindowResponseFilter(data: any) {
547+
if(data.message == 'deliverCredentials' || data.message == 'authFailure') {
548+
return data;
549+
}
550+
}
479551
}

0 commit comments

Comments
 (0)