Instantly share code, notes, and snippets.
Last active
September 16, 2020 13:58
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save doeringp/fbdd980c92d9e08a7863b2cde63b4db9 to your computer and use it in GitHub Desktop.
Sample AuthorizeService (wrapper for oidc-client.js) of Microsoft from the ASP.NET Core 3.1 Angular template #Angular #OpenIDConnect
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import { User, UserManager, WebStorageStateStore } from 'oidc-client'; | |
import { BehaviorSubject, concat, from, Observable } from 'rxjs'; | |
import { filter, map, mergeMap, take, tap } from 'rxjs/operators'; | |
import { ApplicationPaths, ApplicationName } from './api-authorization.constants'; | |
export type IAuthenticationResult = | |
SuccessAuthenticationResult | | |
FailureAuthenticationResult | | |
RedirectAuthenticationResult; | |
export interface SuccessAuthenticationResult { | |
status: AuthenticationResultStatus.Success; | |
state: any; | |
} | |
export interface FailureAuthenticationResult { | |
status: AuthenticationResultStatus.Fail; | |
message: string; | |
} | |
export interface RedirectAuthenticationResult { | |
status: AuthenticationResultStatus.Redirect; | |
} | |
export enum AuthenticationResultStatus { | |
Success, | |
Redirect, | |
Fail | |
} | |
export interface IUser { | |
name?: string; | |
} | |
@Injectable({ | |
providedIn: 'root' | |
}) | |
export class AuthorizeService { | |
// By default pop ups are disabled because they don't work properly on Edge. | |
// If you want to enable pop up authentication simply set this flag to false. | |
private popUpDisabled = true; | |
private userManager: UserManager; | |
private userSubject: BehaviorSubject<IUser | null> = new BehaviorSubject(null); | |
public isAuthenticated(): Observable<boolean> { | |
return this.getUser().pipe(map(u => !!u)); | |
} | |
public getUser(): Observable<IUser | null> { | |
return concat( | |
this.userSubject.pipe(take(1), filter(u => !!u)), | |
this.getUserFromStorage().pipe(filter(u => !!u), tap(u => this.userSubject.next(u))), | |
this.userSubject.asObservable()); | |
} | |
public getAccessToken(): Observable<string> { | |
return from(this.ensureUserManagerInitialized()) | |
.pipe(mergeMap(() => from(this.userManager.getUser())), | |
map(user => user && user.access_token)); | |
} | |
// We try to authenticate the user in three different ways: | |
// 1) We try to see if we can authenticate the user silently. This happens | |
// when the user is already logged in on the IdP and is done using a hidden iframe | |
// on the client. | |
// 2) We try to authenticate the user using a PopUp Window. This might fail if there is a | |
// Pop-Up blocker or the user has disabled PopUps. | |
// 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional | |
// redirect flow. | |
public async signIn(state: any): Promise<IAuthenticationResult> { | |
await this.ensureUserManagerInitialized(); | |
let user: User = null; | |
try { | |
user = await this.userManager.signinSilent(this.createArguments()); | |
this.userSubject.next(user.profile); | |
return this.success(state); | |
} catch (silentError) { | |
// User might not be authenticated, fallback to popup authentication | |
console.log('Silent authentication error: ', silentError); | |
try { | |
if (this.popUpDisabled) { | |
throw new Error('Popup disabled. Change \'authorize.service.ts:AuthorizeService.popupDisabled\' to false to enable it.'); | |
} | |
user = await this.userManager.signinPopup(this.createArguments()); | |
this.userSubject.next(user.profile); | |
return this.success(state); | |
} catch (popupError) { | |
if (popupError.message === 'Popup window closed') { | |
// The user explicitly cancelled the login action by closing an opened popup. | |
return this.error('The user closed the window.'); | |
} else if (!this.popUpDisabled) { | |
console.log('Popup authentication error: ', popupError); | |
} | |
// PopUps might be blocked by the user, fallback to redirect | |
try { | |
await this.userManager.signinRedirect(this.createArguments(state)); | |
return this.redirect(); | |
} catch (redirectError) { | |
console.log('Redirect authentication error: ', redirectError); | |
return this.error(redirectError); | |
} | |
} | |
} | |
} | |
public async completeSignIn(url: string): Promise<IAuthenticationResult> { | |
try { | |
await this.ensureUserManagerInitialized(); | |
const user = await this.userManager.signinCallback(url); | |
this.userSubject.next(user && user.profile); | |
return this.success(user && user.state); | |
} catch (error) { | |
console.log('There was an error signing in: ', error); | |
return this.error('There was an error signing in.'); | |
} | |
} | |
public async signOut(state: any): Promise<IAuthenticationResult> { | |
try { | |
if (this.popUpDisabled) { | |
throw new Error('Popup disabled. Change \'authorize.service.ts:AuthorizeService.popupDisabled\' to false to enable it.'); | |
} | |
await this.ensureUserManagerInitialized(); | |
await this.userManager.signoutPopup(this.createArguments()); | |
this.userSubject.next(null); | |
return this.success(state); | |
} catch (popupSignOutError) { | |
console.log('Popup signout error: ', popupSignOutError); | |
try { | |
await this.userManager.signoutRedirect(this.createArguments(state)); | |
return this.redirect(); | |
} catch (redirectSignOutError) { | |
console.log('Redirect signout error: ', popupSignOutError); | |
return this.error(redirectSignOutError); | |
} | |
} | |
} | |
public async completeSignOut(url: string): Promise<IAuthenticationResult> { | |
await this.ensureUserManagerInitialized(); | |
try { | |
const response = await this.userManager.signoutCallback(url); | |
this.userSubject.next(null); | |
return this.success(response && response.state); | |
} catch (error) { | |
console.log(`There was an error trying to log out '${error}'.`); | |
return this.error(error); | |
} | |
} | |
private createArguments(state?: any): any { | |
return { useReplaceToNavigate: true, data: state }; | |
} | |
private error(message: string): IAuthenticationResult { | |
return { status: AuthenticationResultStatus.Fail, message }; | |
} | |
private success(state: any): IAuthenticationResult { | |
return { status: AuthenticationResultStatus.Success, state }; | |
} | |
private redirect(): IAuthenticationResult { | |
return { status: AuthenticationResultStatus.Redirect }; | |
} | |
private async ensureUserManagerInitialized(): Promise<void> { | |
if (this.userManager !== undefined) { | |
return; | |
} | |
const response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl); | |
if (!response.ok) { | |
throw new Error(`Could not load settings for '${ApplicationName}'`); | |
} | |
const settings: any = await response.json(); | |
settings.automaticSilentRenew = true; | |
settings.includeIdTokenInSilentRenew = true; | |
this.userManager = new UserManager(settings); | |
this.userManager.events.addUserSignedOut(async () => { | |
await this.userManager.removeUser(); | |
this.userSubject.next(null); | |
}); | |
} | |
private getUserFromStorage(): Observable<IUser> { | |
return from(this.ensureUserManagerInitialized()) | |
.pipe( | |
mergeMap(() => this.userManager.getUser()), | |
map(u => u && u.profile)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment