Skip to content

Commit

Permalink
feat(auth): move token refresh to auth guard
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathonadams committed Sep 4, 2020
1 parent 0a9a74f commit 3a57446
Show file tree
Hide file tree
Showing 12 changed files with 55 additions and 94 deletions.
8 changes: 0 additions & 8 deletions apps/demo/demo-web/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import {
} from '@ztp/common/data-access';
import {
CommonAuthDataAccessModule,
authProviderFactory,
AuthService,
LOGIN_PAGE,
REGISTER_PAGE,
LOGIN_REDIRECT,
Expand Down Expand Up @@ -86,12 +84,6 @@ export const APP_ERRORS = {
multi: true,
deps: [ThemeService],
},
{
provide: APP_INITIALIZER,
useFactory: authProviderFactory,
multi: true,
deps: [AuthService],
},
],
bootstrap: [AppComponent],
})
Expand Down
2 changes: 1 addition & 1 deletion apps/demo/demo-web/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.

const serverUrl = 'http://localhost:3000';
const serverUrl = 'http://localhost:3020';

export const environment = {
production: false,
Expand Down
16 changes: 2 additions & 14 deletions apps/todos/todos-web/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
Expand All @@ -10,11 +10,7 @@ import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';

import { CommonDataAccessModule } from '@ztp/common/data-access';
import {
CommonAuthDataAccessModule,
authProviderFactory,
AuthService,
} from '@ztp/common/auth/data-access';
import { CommonAuthDataAccessModule } from '@ztp/common/auth/data-access';
import { SharedUsersDataAccessModule } from '@ztp/shared/users/data-access';
import { TodosFeatureShellModule } from '@ztp/todos/feature-shell';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
Expand Down Expand Up @@ -57,13 +53,5 @@ export const APP_ERRORS = {
TodosFeatureShellModule,
],
bootstrap: [AppComponent],
providers: [
{
provide: APP_INITIALIZER,
useFactory: authProviderFactory,
multi: true,
deps: [AuthService],
},
],
})
export class AppModule {}
2 changes: 1 addition & 1 deletion libs/common/auth/data-access/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { CommonAuthDataAccessModule } from './lib/common-auth-data-access.module
export { AuthEffects } from './lib/+state/auth.effects';
export { AuthGuard } from './lib/guards/auth.guard';
export { AuthInterceptor } from './lib/interceptors/auth-interceptor';
export { AuthService, authProviderFactory } from './lib/services/auth.service';
export { AuthService } from './lib/services/auth.service';
export { AuthFacade } from './lib/+state/auth.facade';
export {
ILoginCredentials,
Expand Down
9 changes: 1 addition & 8 deletions libs/common/auth/data-access/src/lib/+state/auth.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ export const login = createAction(
props<ILoginCredentials>()
);

export const isLoggedIn = createAction(
'[Auth/API] Is Logged In',
props<{ originalUrl: string }>()
);
// no op action
export const isLoggedFail = createAction('[Auth/API] Is Logged In Fail');

export const loginSuccess = createAction(
'[Auth/API] Login Success',
props<ILoginResponse>()
Expand Down Expand Up @@ -45,7 +38,7 @@ export const registerFailure = createAction(

export const setAuthenticated = createAction(
'[Auth] Set Authenticated',
props<{ expiresIn: number; token: string; navigate?: string }>()
props<{ expiresIn: number; token: string }>()
);

export const logout = createAction('[Auth] Logout');
Expand Down
33 changes: 0 additions & 33 deletions libs/common/auth/data-access/src/lib/+state/auth.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,39 +48,6 @@ export class AuthEffects {
{ dispatch: false }
);

isLoggedIn$ = createEffect(() =>
this.actions$.pipe(
ofType(AuthActions.isLoggedIn),
switchMap(({ originalUrl }) =>
this.authService.refreshAccessToken().pipe(
map(({ token, expiresIn }) => {
if (token && expiresIn) {
return AuthActions.setAuthenticated({
token,
expiresIn,
navigate: originalUrl,
});
} else {
return AuthActions.isLoggedFail();
}
}),
catchError((e) => of(AuthActions.isLoggedFail()))
)
)
)
);

setAuthenticated$ = createEffect(
() =>
this.actions$.pipe(
ofType(AuthActions.setAuthenticated),
tap(({ navigate }) => {
if (navigate) this.router.navigate([navigate]);
})
),
{ dispatch: false }
);

register$ = createEffect(
() =>
this.actions$.pipe(
Expand Down
6 changes: 2 additions & 4 deletions libs/common/auth/data-access/src/lib/+state/auth.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { ILoginCredentials, IRegistrationDetails } from '../auth.interface';

@Injectable({ providedIn: 'root' })
export class AuthFacade {
init$: Observable<boolean>;
accessToken$: Observable<string | null>;
expiresAt$: Observable<number | null>;
authenticated$: Observable<boolean>;
user$: Observable<IUser | null>;

constructor(private store: Store<any>) {
this.init$ = this.store.pipe(select(fromAuth.selectInit));
this.accessToken$ = this.store.pipe(select(fromAuth.selectAccessToken));
this.expiresAt$ = this.store.pipe(select(fromAuth.selectExpiresAt));
this.authenticated$ = this.store.pipe(select(fromAuth.selectAuthenticated));
Expand All @@ -36,10 +38,6 @@ export class AuthFacade {
this.store.dispatch(AuthActions.loadAuthUser());
}

isLoggedIn(url: string): void {
this.store.dispatch(AuthActions.isLoggedIn({ originalUrl: url }));
}

setAuthenticated(auth: { token: string; expiresIn: number }) {
this.store.dispatch(AuthActions.setAuthenticated(auth));
}
Expand Down
4 changes: 4 additions & 0 deletions libs/common/auth/data-access/src/lib/+state/auth.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { IUser } from '@ztp/data';
export const authStateKey = 'authStateKey';

export interface AuthState {
init: boolean;
accessToken: string | null;
authenticated: boolean;
expiresAt: number | null;
user: IUser | null;
}

export const initialState: AuthState = {
init: false,
accessToken: null,
authenticated: false,
expiresAt: null,
Expand All @@ -25,6 +27,7 @@ const authReducer = createReducer(
fromAuth.setAuthenticated,
(state, { token, expiresIn }) => {
return {
init: true,
user: null,
accessToken: token,
authenticated: true,
Expand All @@ -34,6 +37,7 @@ const authReducer = createReducer(
),
on(fromAuth.logout, (state) => {
return {
init: true,
expiresAt: null,
authenticated: false,
user: null,
Expand Down
2 changes: 2 additions & 0 deletions libs/common/auth/data-access/src/lib/+state/auth.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const selectAuthSate = createFeatureSelector<fromAuth.AuthState>(
fromAuth.authStateKey
);

export const selectInit = createSelector(selectAuthSate, (state) => state.init);

export const selectAuthenticated = createSelector(
selectAuthSate,
(state) => state.authenticated
Expand Down
35 changes: 29 additions & 6 deletions libs/common/auth/data-access/src/lib/guards/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
import { Injectable, Inject, Optional } from '@angular/core';
import { CanActivate, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { map, timeout, switchMap, catchError } from 'rxjs/operators';
import { AuthFacade } from '../+state/auth.facade';
import { LOGIN_PAGE } from '../tokens/tokens';
import { AuthService } from '../services/auth.service';

// time in milliseconds
const INITIAL_LOAD_TIMEOUT_TIME = 500;

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private facade: AuthFacade,
private service: AuthService,
private router: Router,
@Optional() @Inject(LOGIN_PAGE) private login: string
) {
this.login = login ? login : '/login';
}

canActivate(): Observable<boolean | UrlTree> {
return this.facade.authenticated$.pipe(
map((loggedIn) =>
loggedIn ? loggedIn : this.router.parseUrl(this.login)
)
return this.facade.init$.pipe(
switchMap((init) => {
if (init) {
return this.facade.authenticated$.pipe(
map((loggedIn) =>
loggedIn ? loggedIn : this.router.parseUrl(this.login)
)
);
} else {
return this.service.initLogin().pipe(
timeout(INITIAL_LOAD_TIMEOUT_TIME),
map(({ token, expiresIn }) => {
if (token && expiresIn) {
return true;
} else {
return this.router.parseUrl(this.login);
}
}),
catchError((e) => of(this.router.parseUrl(this.login)))
);
}
})
);
}
}
24 changes: 9 additions & 15 deletions libs/common/auth/data-access/src/lib/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
import { AuthFacade } from '../+state/auth.facade';
import { jwtDecode } from './jwt-decode';
import { AUTH_SERVER_URL } from '../tokens/tokens';
import { Router } from '@angular/router';
import { DOCUMENT } from '@angular/common';
import { tap } from 'rxjs/operators';

interface GQLSuccess<T> {
data: T;
Expand All @@ -35,8 +34,7 @@ export class AuthService {
constructor(
@Inject(AUTH_SERVER_URL) private serverUrl: string,
private http: HttpClient,
private facade: AuthFacade,
@Inject(DOCUMENT) private document: Document
private facade: AuthFacade
) {}

// Login function that returns a JWT
Expand Down Expand Up @@ -148,13 +146,13 @@ export class AuthService {
);
}

public isLoggedIn() {
const originalUrl = this.document.location.pathname;
this.facade.isLoggedIn(originalUrl);
}

isAuthenticated(expiration: number): boolean {
return new Date().valueOf() < expiration;
public initLogin() {
return this.refreshAccessToken().pipe(
tap(({ token, expiresIn }) => {
if (token && expiresIn)
this.facade.setAuthenticated({ token, expiresIn });
})
);
}

decodeToken(token: string | null) {
Expand All @@ -168,7 +166,3 @@ export class AuthService {
};
}
}

export function authProviderFactory(service: AuthService) {
return () => service.isLoggedIn();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const TODOS_ROUTES: Routes = [
{
path: '',
component: TodoFeatureShellComponent,
canActivate: [AuthGuard],
data: {
animation: 'AppPages',
},
children: [
{
path: 'home',
Expand Down Expand Up @@ -53,10 +57,6 @@ export const TODOS_ROUTES: Routes = [
redirectTo: 'home',
},
],
canActivate: [AuthGuard],
data: {
animation: 'AppPages',
},
},
{
path: '',
Expand Down

0 comments on commit 3a57446

Please sign in to comment.