Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fabiangosebrink/create popup example with better API #960

Merged
merged 20 commits into from
Feb 7, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
282 changes: 149 additions & 133 deletions angular.json

Large diffs are not rendered by default.

34 changes: 25 additions & 9 deletions docs/authorizing-popup-iframe.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
# Authorizing in a popup or iframe
# Authorizing in a popup
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authentication using a Popup


You can call the Provider's authorization endpoint in a popup or iframe instead of navigating to it in the app's parent window.
This allows you to have the Provider's consent prompt display in a popup window to avoid unloading and reloading the app,
or to authorize the user silently by loading the endpoint in a hidden iframe if that supported by the Provider.
You can call the Provider's authorization endpoint in a popup instead of navigating to it in the app's parent window.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can authenticate tith any Open ID Connect identity provider using a popup.

This allows you to have the provider's consent prompt display in a popup window to avoid unloading and reloading the app.

To get the fully-formed authorization URL, pass a handler function to `OidcSecurityService.authorize`
(this will also prevent the default behavior of loading the authorization endpoint in the current window):
Sample:

```typescript
login() {
this.oidcSecurityService.authorizeWithPopUp();
}
userData$: Observable<any>;

isAuthenticated: boolean;

constructor(public oidcSecurityService: OidcSecurityService) {}

ngOnInit() {
this.oidcSecurityService.checkAuth().subscribe((isAuthenticated) => {
console.log('app authenticated', isAuthenticated);
const at = this.oidcSecurityService.getToken();
console.log(`Current access token is '${at}'`);
});
}

loginWithPopup() {
this.oidcSecurityService.authorizeWithPopUp().subscribe(({ isAuthenticated, userData, accessToken }) => {
console.log(isAuthenticated);
console.log(userData);
console.log(accessToken);
});
}
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"coveralls": "cat ./coverage/angular-auth-oidc-client/lcov.info | coveralls",
"start-sample-code-flow-auto-login": "ng serve sample-code-flow-auto-login --ssl -o",
"start-sample-code-flow": "ng serve sample-code-flow --ssl -o",
"start-sample-code-flow-popup": "ng serve sample-code-flow-popup --ssl -o",
"start-sample-code-flow-http-config": "ng serve sample-code-flow-http-config --ssl -o",
"start-sample-code-flow-refresh-tokens": "ng serve sample-code-flow-refresh-tokens --ssl -o",
"start-sample-code-flow-azure-b2c": "ng serve sample-code-flow-azure-b2c --ssl -o",
Expand Down
2 changes: 2 additions & 0 deletions projects/angular-auth-oidc-client/src/lib/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NgModule } from '@angular/core';
import { DataService } from './api/data.service';
import { HttpBaseService } from './api/http-base.service';
import { AuthStateService } from './authState/auth-state.service';
import { CheckAuthService } from './check-auth.service';
import { ConfigValidationService } from './config-validation/config-validation.service';
import { AuthWellKnownDataService } from './config/auth-well-known-data.service';
import { AuthWellKnownService } from './config/auth-well-known.service';
Expand Down Expand Up @@ -72,6 +73,7 @@ export class AuthModule {
DataService,
StateValidationService,
ConfigValidationService,
CheckAuthService,
{
provide: AbstractSecurityStorage,
useClass: token.storage || BrowserStorageService,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable()
export class CheckAuthServiceMock {
checkAuth(url?: string): Observable<boolean> {
return of(null);
}

checkAuthIncludingServer(): Observable<boolean> {
return of(null);
}
}
255 changes: 255 additions & 0 deletions projects/angular-auth-oidc-client/src/lib/check-auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { HttpClientModule } from '@angular/common/http';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { BrowserModule } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { of, throwError } from 'rxjs';
import { AuthModule } from './auth.module';
import { AuthStateService } from './authState/auth-state.service';
import { CallbackService } from './callback/callback.service';
import { PeriodicallyTokenCheckService } from './callback/periodically-token-check.service';
import { RefreshSessionService } from './callback/refresh-session.service';
import { ConfigurationProvider } from './config/config.provider';
import { CheckSessionService } from './iframe/check-session.service';
import { SilentRenewService } from './iframe/silent-renew.service';
import { LoggerService } from './logging/logger.service';
import { LoggerServiceMock } from './logging/logger.service-mock';
import { PopUpService } from './login/popup.service';
import { OidcSecurityService } from './oidc.security.service';
import { UserService } from './userData/user-service';

describe('CheckAuthService', () => {
let oidcSecurityService: OidcSecurityService;
let configurationProvider: ConfigurationProvider;
let authStateService: AuthStateService;
let userService: UserService;
let checkSessionService: CheckSessionService;
let callBackService: CallbackService;
let silentRenewService: SilentRenewService;
let periodicallyTokenCheckService: PeriodicallyTokenCheckService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [BrowserModule, HttpClientModule, RouterTestingModule, AuthModule.forRoot()],
providers: [
CheckSessionService,
SilentRenewService,
UserService,
{ provide: LoggerService, useClass: LoggerServiceMock },
ConfigurationProvider,
AuthStateService,
CallbackService,
RefreshSessionService,
PeriodicallyTokenCheckService,
PopUpService,
],
});
});

beforeEach(() => {
oidcSecurityService = TestBed.inject(OidcSecurityService);
configurationProvider = TestBed.inject(ConfigurationProvider);
userService = TestBed.inject(UserService);
authStateService = TestBed.inject(AuthStateService);
checkSessionService = TestBed.inject(CheckSessionService);
callBackService = TestBed.inject(CallbackService);
silentRenewService = TestBed.inject(SilentRenewService);

periodicallyTokenCheckService = TestBed.inject(PeriodicallyTokenCheckService);
});

it('should create', () => {
expect(oidcSecurityService).toBeTruthy();
});

describe('checkAuth', () => {
it(
'returns false when config is not valid',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(false);
oidcSecurityService.checkAuth().subscribe((result) => expect(result).toBeFalse());
})
);

it(
'returns false in case handleCallbackAndFireEvents throws an error',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'isCallback').and.returnValue(true);
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);
const spy = spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(throwError('ERROR'));
oidcSecurityService.checkAuth().subscribe((result) => {
expect(result).toBeFalse();
expect(spy).toHaveBeenCalled();
});
})
);

it(
'calls callbackService.handlePossibleStsCallback with current url when callback is true',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'isCallback').and.returnValue(true);
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);
const spy = spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
oidcSecurityService.checkAuth().subscribe((result) => {
expect(result).toBeTrue();
expect(spy).toHaveBeenCalled();
});
})
);

it(
'does NOT call handleCallbackAndFireEvents with current url when callback is false',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'isCallback').and.returnValue(false);
const spy = spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
oidcSecurityService.checkAuth().subscribe((result) => {
expect(result).toBeFalse();
expect(spy).not.toHaveBeenCalled();
});
})
);

it(
'does fire the auth and user data events when it is not a callback from the sts and is authenticated',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'isCallback').and.returnValue(false);
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));

const setAuthorizedAndFireEventSpy = spyOn(authStateService, 'setAuthorizedAndFireEvent');
const userServiceSpy = spyOn(userService, 'publishUserDataIfExists');
oidcSecurityService.checkAuth().subscribe((result) => {
expect(result).toBeTrue();
expect(setAuthorizedAndFireEventSpy).toHaveBeenCalled();
expect(userServiceSpy).toHaveBeenCalled();
});
})
);

it(
'does NOT fire the auth and user data events when it is not a callback from the sts and is NOT authenticated',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'isCallback').and.returnValue(false);
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(false);
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));

const setAuthorizedAndFireEventSpy = spyOn(authStateService, 'setAuthorizedAndFireEvent');
const userServiceSpy = spyOn(userService, 'publishUserDataIfExists');
oidcSecurityService.checkAuth().subscribe((result) => {
expect(result).toBeFalse();
expect(setAuthorizedAndFireEventSpy).not.toHaveBeenCalled();
expect(userServiceSpy).not.toHaveBeenCalled();
});
})
);

it(
'if authenticated return true',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);

oidcSecurityService.checkAuth().subscribe((result) => {
expect(result).toBeTrue();
});
})
);

it(
'if authenticated set auth and fires event ',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);

const spy = spyOn(authStateService, 'setAuthorizedAndFireEvent');

oidcSecurityService.checkAuth().subscribe((result) => {
expect(spy).toHaveBeenCalled();
});
})
);

it(
'if authenticated publishUserdataIfExists ',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);

const spy = spyOn(userService, 'publishUserDataIfExists');

oidcSecurityService.checkAuth().subscribe((result) => {
expect(spy).toHaveBeenCalled();
});
})
);

it(
'if authenticated callbackService startTokenValidationPeriodically',
waitForAsync(() => {
const config = {
stsServer: 'stsServer',
tokenRefreshInSeconds: 7,
};
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue(config);
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);

const spy = spyOn(periodicallyTokenCheckService, 'startTokenValidationPeriodically');

oidcSecurityService.checkAuth().subscribe((result) => {
expect(spy).toHaveBeenCalledWith(7);
});
})
);

it(
'if isCheckSessionConfigured call checkSessionService.start()',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);

spyOn(checkSessionService, 'isCheckSessionConfigured').and.returnValue(true);
const spy = spyOn(checkSessionService, 'start');

oidcSecurityService.checkAuth().subscribe((result) => {
expect(spy).toHaveBeenCalled();
});
})
);

it(
'if isSilentRenewConfigured call getOrCreateIframe()',
waitForAsync(() => {
spyOn(configurationProvider, 'hasValidConfig').and.returnValue(true);
spyOnProperty(configurationProvider, 'openIDConfiguration', 'get').and.returnValue('stsServer');
spyOn(callBackService, 'handleCallbackAndFireEvents').and.returnValue(of(null));
spyOn(authStateService, 'areAuthStorageTokensValid').and.returnValue(true);

spyOn(silentRenewService, 'isSilentRenewConfigured').and.returnValue(true);
const spy = spyOn(silentRenewService, 'getOrCreateIframe');

oidcSecurityService.checkAuth().subscribe((result) => {
expect(spy).toHaveBeenCalled();
});
})
);
});
});