I was given a set of files to be used for basic authentication on an Angular application and it works pretty fine with the backend and DB, but I'd like to get my credentials once for all when I sign in so I can use them to get some other data stored in my DB. Currently, I have to call a function on my AuthService on every component if I need to get account data, but even if it works I think that repeating the same code everywhere is not a good practice so I ask for your help. I tried to use Observables and BehaviorSubjects as I found on the internet but I haven't been able to make them work.
authService.ts :
@Injectable({
providedIn: 'root'
})
export class AuthService extends ApiService {
isAuthenticated = false;
constructor(public tokenService: TokenService, public override http: HttpService, public navigation: NavigationService) {
super(http);
}
signin(payload: SigninPayload): Observable<ApiResponse> {
return this.http.post(`${this.baseUrl}${ApiUriEnum.SIGNIN}`, payload).pipe(
map((response: ApiResponse) => {
if (response.result) {
const signinResponse: SigninResponse = response.data as SigninResponse;
this.tokenService.saveToken(signinResponse.token.access_token);
this.tokenService.saveRefreshToken(signinResponse.token.refresh_token);
this.isAuthenticated = true;
this.navigation.navigateToSecure();
}
return response;
})
)
}
me(): Observable<ApiResponse> {
return this.http.get(`${this.baseUrl}${ApiUriEnum.ME}`);
}
signup(): Observable<ApiResponse> {
return of({result: true, data: null, error_code: null})
}
refreshToken(refresh: RefreshPayload): Observable<ApiResponse> {
return this.http.post(`${this.baseUrl}${ApiUriEnum.REFRESH_TOKEN}`, refresh).pipe(
map((response: ApiResponse) => {
if (response.result) {
const tokenResponse: TokenDto = response.data as TokenDto;
this.tokenService.saveToken(tokenResponse.access_token);
this.tokenService.saveRefreshToken(tokenResponse.refresh_token);
this.isAuthenticated = true;
}
return response;
})
)
}
logout(): void {
this.tokenService.signOut();
this.isAuthenticated = false;
this.navigation.navigateToUnsecure();
}
}
dashboard.component.ts :
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
credential?: Credential;
constructor(public auth: AuthService) {
}
ngOnInit(): void {
}
me(): void {
this.auth.me().subscribe((response: ApiResponse) => {
this.credential = CredentialHelper.credentialFromDto(response.data as CredentialDto);
console.log('this.credential', this.credential);
})
}
logout(): void {
this.auth.logout();
}
}
api-response.interface.ts:
export interface ApiResponse extends DtoInterface{
result: boolean;
data: DtoInterface | DtoInterface[] | null;
error_code: string | null;
}
You can use two options. One is easy and straightforward but not secure and it is naive. The other is more fine (complicated) but it does not leave trace about your state management flow.
Option one:
You use localStorage API. A browser supported API that persists data on the client side even with window refresh. Read about the four methods you can use with this API (setItem, getItem, removeItem, clear) here on MDN
Option two:
You use the service mechanism as a singelton pattern to store session values inside the Observer
pattern (Subject
) which will broadcast the values to subscribers automatically based on values change. This is very classy and solid way of managing your sensitive information without interacting with weak APIs like localStorage
. However, as mentioned you will need to understand service injection and also how Observables
and Subjects
work together so you communicate values between notifiers
and listeners
correctly.
A very important point here, which is that state stored in Subjects gets destoryed by some browsers events such as window refresh. You need to take this into account. For example, if your AuthToken
is stored in a Subject and there is an AuthGuard
watching for this AuthToken
and you refresh the window you will lose the state and the AuthGuard
will notice that the token has been destoryed. Thus take this into account if you are to go with the second option.