Skip to content

Instantly share code, notes, and snippets.

@JBreit
Last active July 9, 2018 09:26
Show Gist options
  • Save JBreit/4884d04e2d110dc1e9be976ccfcd2a40 to your computer and use it in GitHub Desktop.
Save JBreit/4884d04e2d110dc1e9be976ccfcd2a40 to your computer and use it in GitHub Desktop.
@ngrx/store authentication example code
import { createFeatureSelector } from '@ngrx/store';
import * as auth from './reducers/auth.reducers';
export interface AppState {
authState: auth.State;
}
export const reducers = {
auth: auth.reducer
};
export const selectAuthState = createFeatureSelector<AppState>('auth');
import { Action } from '@ngrx/store';
import { User } from '../../models/user';
export const LOGIN = '[Auth] Login';
export const LOGIN_SUCCESS = '[Auth] Login Success';
export const LOGIN_FAILURE = '[Auth] Login Failure';
export const LOGIN_REDIRECT = '[Auth] Login Redirect';
export const REGISTER = '[Auth] Register';
export const REGISTER_SUCCESS = '[Auth] Register Success';
export const REGISTER_FAILURE = '[Auth] Register Failure';
export const LOGOUT = '[Auth] Logout';
export const GET_STATUS = '[Auth] GetStatus';
/**
* Authenticate
*
* @class Login
* @implements { Action }
*/
export class Login implements Action {
readonly type = LOGIN;
constructor(public payload: User) {}
}
/**
* Authenticate
*
* @class LoginSuccess
* @implements { Action }
*/
export class LoginSuccess implements Action {
readonly type = LOGIN_SUCCESS;
constructor(public payload: User) {}
}
/**
* Authenticate
*
* @class LoginFailure
* @implements { Action }
*/
export class LoginFailure implements Action {
readonly type = LOGIN_FAILURE;
constructor(public payload: { error: string }) {}
}
/**
* Authenticate
*
* @class LoginRedirect
* @implements { Action }
*/
export class LoginRedirect implements Action {
readonly type = LOGIN_REDIRECT;
}
/**
* Authenticate
*
* @class Register
* @implements { Action }
*/
export class Register implements Action {
readonly type = REGISTER;
constructor(public payload: User) {}
}
/**
* Authenticate
*
* @class RegisterSuccess
* @implements { Action }
*/
export class RegisterSuccess implements Action {
readonly type = REGISTER_SUCCESS;
constructor(public payload: User) {}
}
/**
* Authenticate
*
* @class RegisterFailure
* @implements { Action }
*/
export class RegisterFailure implements Action {
readonly type = REGISTER_FAILURE;
constructor(public payload: { error: string }) {}
}
/**
* Authenticate
*
* @class Logout
* @implements { Action }
*/
export class Logout implements Action {
readonly type = LOGOUT;
}
/**
* Authenticate
*
* @class GetStatus
* @implements { Action }
*/
export class GetStatus implements Action {
readonly type = GET_STATUS;
}
/**
* Actions type
*
* @type { Actions }
*/
export type All =
| Login
| LoginSuccess
| LoginFailure
| LoginRedirect
| Register
| RegisterSuccess
| RegisterFailure
| Logout
| GetStatus;
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Observable, of } from 'rxjs';
import { catchError, debounceTime, map, switchMap, tap } from 'rxjs/operators';
import { AuthService } from '../../services/auth.service';
import { User } from '../../models/user';
import {
LOGIN,
LOGIN_SUCCESS,
LOGIN_FAILURE,
LOGIN_REDIRECT,
REGISTER,
REGISTER_SUCCESS,
REGISTER_FAILURE,
LOGOUT,
GET_STATUS,
Login,
LoginSuccess,
LoginFailure,
Register,
RegisterSuccess,
RegisterFailure,
Logout,
GetStatus
} from '../actions/auth.actions';
/**
* @class AuthEffects
*/
@Injectable()
export class AuthEffects {
/**
* @constructor
* @param { Actions } actions
* @param { AuthService } authService
* @param { Router } router
* @param { HttpClient } http
*/
constructor(
private actions$: Actions,
private authService: AuthService,
private router: Router,
private http: HttpClient
) {}
/**
* [Auth] Login
*
* @param { [type] } ofType(LOGIN) [description]
* @return Authenticated user or Error
*/
@Effect()
Login: Observable<any> = this.actions$.pipe(
ofType(LOGIN),
debounceTime(500),
map((action: Login) => action.payload),
switchMap(payload => {
const { email, password } = payload;
return this.authService.login(email, password).pipe(
map(res => new LoginSuccess({ token: res.token, email })),
catchError(error => of(new LoginFailure({ error })))
);
})
);
/**
* [Auth] Login Success
*
* @param { false } dispatch Login
* @param { [type] } ofType(LOGIN_SUCCESS) [description]
*/
@Effect({ dispatch: false })
LoginSuccess: Observable<any> = this.actions$.pipe(
ofType(LOGIN_SUCCESS),
tap(user => {
const { token } = user.payload;
this.authService.setToken(token);
this.router.navigateByUrl('/');
})
);
/**
* [Auth] Login Failure
*
* @param { false } dispatch LoginFailure
* @param { [type] } ofType(LOGIN_FAILURE) [description]
*/
@Effect({ dispatch: false })
LoginFailure: Observable<any> = this.actions$.pipe(
ofType(LOGIN_FAILURE)
);
/**
* [Auth] Login Redirect
*
* @param { false } dispatch LoginRedirect
* @param { [type] } ofType(LOGIN_REDIRECT, LOGOUT) [description]
*/
@Effect({ dispatch: false })
LoginRedirect: Observable<any> = this.actions$.pipe(
ofType(LOGIN_REDIRECT, LOGOUT),
tap(authed => this.router.navigateByUrl('/login'))
);
/**
* [Auth] Register
*
* @param { false } dispatch Register
* @param { [type] } ofType(REGISTER) [description]
*/
@Effect()
Register: Observable<any> = this.actions$.pipe(
ofType(REGISTER),
map((action: Register) => action.payload),
switchMap(payload => {
const { email, name, password } = payload;
return this.authService.register(email, name, password).pipe(
map(user => new RegisterSuccess({ email, name })),
catchError(error => of(new RegisterFailure({ error })))
);
})
);
/**
* [Auth] Register Success
*
* @param { false } dispatch Register Success
* @param { [type] } ofType(REGISTER_SUCCESS) [description]
*/
@Effect({ dispatch: false })
RegisterSuccess: Observable<any> = this.actions$.pipe(
ofType(REGISTER_SUCCESS),
tap(user => {
const { token } = user.payload;
this.authService.setToken(token);
this.router.navigateByUrl('/');
})
);
/**
* [Auth] Register Failure
*
* @param { false } dispatch Register Failure
* @param { [type] } ofType(REGISTER_FAILURE) [description]
*/
@Effect({ dispatch: false })
RegisterFailure: Observable<any> = this.actions$.pipe(
ofType(REGISTER_FAILURE)
);
/**
* [Auth] Logout
*
* @param { false } dispatch Logout Action
* @param { [type] } ofType(LOGOUT) [description]
*/
@Effect({ dispatch: false })
public Logout: Observable<any> = this.actions$.pipe(
ofType(LOGOUT),
tap(user => {
this.authService.removeToken();
this.router.navigateByUrl('/login');
})
);
/**
* [Auth] GetStatus
*
* @param { false } dispatch Get Status
* @param { [type] } ofType(GET_STATUS) [description]
*/
@Effect({ dispatch: false })
GetStatus: Observable<any> = this.actions$.pipe(
ofType(GET_STATUS),
switchMap(payload => {
return this.authService.getStatus();
})
);
}
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Store } from '@ngrx/store';
import { AuthService } from './auth.service';
import { LoginRedirect } from './../store/actions/auth.actions';
import { AppState } from './../store/app.states';
/**
* Prevent unauthorized activation of routes
* @class AuthGuardService
*/
@Injectable()
export class AuthGuardService implements CanActivate {
/**
* @constructor
* @param { Store } store
* @param { AuthService } authService
*/
constructor(
private store: Store<AppState>,
public authService: AuthService
) {}
/**
* @method canActivate
* @return { boolean } True when user is authenticated
*/
canActivate(): boolean {
if (!this.authService.getToken()) {
this.store.dispatch(new LoginRedirect());
return false;
}
return true;
}
}
import { User } from '../../models/user';
import {
LOGIN,
LOGIN_SUCCESS,
LOGIN_FAILURE,
REGISTER,
REGISTER_SUCCESS,
REGISTER_FAILURE,
LOGOUT,
GET_STATUS,
All
} from '../actions/auth.actions';
/**
* The state.
* @interface State
*/
export interface State {
isAuthenticated: boolean;
user: User | null;
errorMessage: string | null;
}
/**
* The initial state.
*/
export const initialState: State = {
isAuthenticated: false,
user: null,
errorMessage: null
}
/**
* The reducer function.
* @function reducer
* @param { State } state Current state
* @param { Actions } action Incoming action
*/
export function reducer(state = initialState, action: All): State {
switch (action.type) {
case LOGIN_SUCCESS: {
return {
...state,
isAuthenticated: true,
user: {
token: action.payload.token,
email: action.payload.email
},
errorMessage: null
};
}
case LOGIN_FAILURE: {
return {
...state,
errorMessage: 'Incorrected email and/or password.'
};
}
case REGISTER_SUCCESS: {
return {
...state,
isAuthenticated: true,
user: {
email: action.payload.email,
name: action.payload.name
},
errorMessage: null
};
}
case REGISTER_FAILURE: {
return {
...state,
errorMessage: 'That email is already in use.'
}
}
case LOGOUT: {
return initialState;
}
default: {
return state;
}
}
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CookieService } from 'ngx-cookie-service';
import { User } from '../models/user';
/**
* Authentication Service
*
* @class AuthService
*/
@Injectable()
export class AuthService {
private BASE_URL = 'http://innermind/api';
/**
* @constructor
* @param { HttpClient } http
* @param { CookieService } cookieService
*/
constructor(private http: HttpClient, private cookieService: CookieService) {}
/**
* Authenticated User
*
* @method Login
* @param { string } email The user's email address
* @param { string } password The user's password
* @return { Observable<User> } Authenticated user observable
*/
login(email: string, password: string): Observable<any> {
const credentials = { email, password };
const url = `${this.BASE_URL}/login`;
return this.http.post<User>(url, credentials);
}
/**
* Creates a User Account
*
* @method register
* @param { string } email The user's email address
* @param { string } name The user's name
* @param { string } password The user's password
* @return { Observable<User> } Authenticated user observable
*/
register(email: string, name: string, password: string): Observable<User> {
const credentials = { email, name, password };
const url = `${this.BASE_URL}/register`;
return this.http.post<User>(url, credentials);
}
/**
* @method getStatus
* @return { Observable<User> } [description]
*/
getStatus(): Observable<User> {
const url = `${this.BASE_URL}/auth/status`;
return this.http.get<User>(url);
}
/**
* @method getToken
* @return { string } [description]
*/
getToken(): string {
return localStorage.getItem('token');
}
/**
* @method setToken
* @param { [type] } token [description]
*/
setToken(token): void {
return localStorage.setItem('token', token);
}
/**
* @method removeToken
*/
removeToken(): void {
return localStorage.removeItem('token');
}
/**
* @method refreshToken
* @return { Observable<Object> } [description]
*/
refreshToken(): Observable<Object> {
return this.http.get('http://innermind/api/token/refresh');
}
}
import { Injectable, Injector } from '@angular/core';
import {
HttpClient,
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpResponse,
HttpErrorResponse
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
import { map } from 'rxjs/operators';
/**
* HTTP Token Interceptor
*
* @class TokenInterceptor
*/
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
private authService: AuthService;
/**
* @constructor
* @param { Injector } injector
* @param { HttpClient } http
*/
constructor(private injector: Injector, private http: HttpClient) {}
/**
* @method intercept
* @param { HttpRequest<any> } request [description]
* @param { HttpHandler } next [description]
* @return { Observable } [description]
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.authService = this.injector.get(AuthService);
const token: string = this.authService.getToken();
request = request.clone({
setHeaders: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return next.handle(request);
}
}
/**
* HTTP Error Interceptor
* @class ErrorInterceptor
*/
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
private authService: AuthService;
/**
* @constructor
* @param { Injector } injector
* @param { Router } router
*/
constructor(private injector: Injector, private router: Router) {}
/**
* @method intercept
* @param { HttpRequest<any> } request [description]
* @param { HttpHandler } next [description]
* @return { Observable } [description]
*/
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.authService = this.injector.get(AuthService);
return next.handle(request).pipe(
catchError((response: any) => {
if (response instanceof HttpErrorResponse && response.status === 401) {
this.authService.removeToken();
this.router.navigateByUrl('/login');
}
return Observable.throw(response);
})
);
}
}
export class User {
id?: string;
email?: string;
name?: string;
password?: string;
token?: string;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment