i need for advice in role based auth with https://github.com/auth0/angular2-jwt/tree/v1.0 JWT Interceptor.
How can i carry out "admin" role auth with Angular 5 ?
Now i have: after login server send back jwt token with user id in payload and using canActivate, my app check if token exist and then allow to enter secured sites.
#Injectable()
export class EnsureAuthenticated implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean {
if (localStorage.getItem('token')) {
return true;
} else {
this.router.navigateByUrl('/login');
return false;
}
}
}
and my secure rote :
export const SECURE_ROUTES: Routes = [
{ path: 'home', component: HomeComponent, canActivate: [EnsureAuthenticated] },
{ path: 'homeadmin', component: HomeadminComponent, canActivate: [AuthenticatedAdmin] },
];
and after that I wanted to create something like that:
#Injectable()
export class AuthenticatedAdmin implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean {
if ("in token is admin") {
return true;
} else {
this.router.navigateByUrl('/login');
return false;
}
}
}
In this approach i need decode token with https://github.com/auth0/jwt-decode
Do you think this is the correct approach? Please let me know if you have any better solution to this problem.
Yes because JWT encode your data in payload section. If you want to get some property you need decode all payload.
When you analyze code in angular2-jwt you find method in JwtHelper class to get token expiration date. In its implementation find in third line that to extract expiration date you need first decode all token payload.
Example below from angular2-jwt
public getTokenExpirationDate(token: string): Date {
let decoded: any;
decoded = this.decodeToken(token);
if (!decoded.hasOwnProperty('exp')) {
return null;
}
let date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
}
Related
I have a List and Details components in an application and I am trying to navigate to Details component by passing id parameter. However, there is not a reponse or error when calling the following method. I also share the routing.module:
routing.module
const routes: Routes = [
{
path: '',
component: ListComponent,
data: {...}
},
{
path: '/details/:id',
component: DetailsComponent,
data: {...}
}
];
list.component
constructor(private router: Router) {}
details(id) {
// the code hits here and get the id parameter correctly
this.router.navigate(['/details'], {
queryParams: { id: id }
});
}
details.component
constructor(private route: ActivatedRoute) { }
ngOnInit(): void {
this.route.paramMap
.subscribe(params => {
let id = +params.get('id');
});
}
So, what is wrong with this approach? The ngOnInit block of the details page is not fired.
In your route, you have specified '/details/:id' where ID is Router Param not a Query Param.
Thus, if you want to navigate to that url, use this instead:
ListComponent
this.router.navigate(['/details', id])
DetailsComponent
constructor(private route: ActivatedRoute) {}
ngOnInit() {
const id = this.route.snapshot.params.id; // Fetch the ID from your
// current route "/details/:id"
}
or you can also do it this way
ngOnInit() {
this.route.params.subscribe(params => console.log(params.id))
}
More info on Angular Router Documentation
you have to add queryParamsHandling: 'merge' to your code
details(id) {
// the code hits here and get the id parameter correctly
this.router.navigate(['/details'], {
queryParams: { id: id },
queryParamsHandling: 'merge'
});
}
Hello in the app component.html
Please add
<router-outlet></router-outlet>
I'm developping a single app and at the moment the only good behavior is that I'm getting an user from an API with HttpClient method.
The method is store in a service.
Getting the user is a success but now I want to get a specific array from that user to re-use it by my will.
Should I make another service since this value will be use in 2 components ?
How should I procced to get this array in a var ?
Exemple of user object :
{
firstName: '',
lastName: '',
arrayIWant: []
}
My user is in a subject and here is the way I use it in a component
user: User;
userSubscription: Subscription;
constructor(
public userService: UserService
) {
}
ngOnInit() {
this.userSubscription = this.userService.userSubject.subscribe(
(user: User) => {
this.user = user;
}
);
this.userService.getSingleUserFromServer();
this.userService.emitUser();
}
ngOnDestroy() {
this.userSubscription.unsubscribe();
}
Should I put this code in every component where I want to use the user or is there a way to definie globaly the user ?
You can use a BehaviourSubject which will hold the last value of whatever that service populates the userSubject with
public userSubject: BehaviourSubject<User> = new BehaviourSubject(null);
getSingleUserFromServer(): void {
//get your user from http
userSubject.next(result);
}
In you HTML you can use the async pipe to display the values of the inner array you want. Or just use it in your component by subscribing to the last emission of the behaviourSubject
//X.Component
public subscriptionKiller: Subject<void> = new Subject();
ngOnInit(): void {
this.userService.userSubject
.pipe(takeUntil(this.subscriptionKiller))
.subscribe((lastUser: User) => {
someMethod(this.userService.userSubject.value.arrayIWant);
}
}
ngOnDestroy(): void {
this.subscriptionKiller.next()
}
I am trying to create a config.json file with just a few urls and read the data in a Service.
.../.../assets/config.json
{
"registration" : "localhost:4200/registration"
"login" : "localhost:4200/login"
}
../services/config.service.ts
export class ConfigService {
result;
configUrl = '../../assets/config.json';
constructor(private http: HttpClient) {}
getConfig() {
return this.http.get(this.configUrl).subscribe((data) => { this.result = data });
}
}
In the specific login.service.ts and registration.service.ts I call the getConfig() to handle the specific urls. The problem is that in this services the return value is undefined but I need the result out of the subscribe/getConfig method.
Now I am watching about 3 hours for a solution but I do get much more confused as more as I read so I would like to ask for help.
I saw solutions with .map() method (but I guess this method does not exist anymore), with "Promises", with export interface but nothing worked.
example for ../registration.service.ts
export class RegistrationService {
private api_url;
constructor(private http: HttpClientModule, private cs: ConfigService) {
this.api_url = this.cs.getConfig();
}
}
Your Config Service:
getConfig() {
return this.http.get(this.configUrl).toPromise();
}
Your Registration Service :
async exampleMethod() {
try {
this.api_url = await this.cs.getConfig();
console.log(this.api_url);
} catch(e) {
throw Error(e);
}
}
I am trying to implement a shared service for managing Roles on my app, with an Observable so that, from other components, you can either change the current role and be notified when it changes. The problem I have is that when I publish a new value through the service, the components that subscribe to the observable always recieve the same value (the initial one). Then, I never receive the new role number and I can't update the component state.
Apparently
I have the following set of components:
RolesService: The shared Service, which manages role change, gets the available roles from the user token, manages persistence of the current role for the logged in user. It uses localStorage to persist the role index. It receives changes
HeaderComponent: This is an example of a component receiving changes for the role change, because it needs to update the title of the user. It subscribes to the observable and changes the title accordingly
EscullRolComponent: And this is an example of a component that changes the role the user is currently using (by action of the user, of course). It has some buttons and sends to the service the new index.
Here is the relevant code for this issue:
// RolesService file
#Injectable()
export class RolesService {
private _currentRole: BehaviorSubject<Rol> = new BehaviorSubject(null);
currentRole = this._currentRole.asObservable();
private get currentIndex(): number {
const ras = localStorage.getItem('current_role');
// console.log("Guardat aixo: ", ras);
if (ras === '' || ras === null || ras === 'NaN' || ras === '-1' || parseInt(ras, 10) === NaN) {
return 0;
} else {
return parseInt(ras, 10);
}
}
private set currentIndex(val) {
localStorage.setItem('current_role', val.toString());
}
currentToken: NbAuthJWTToken;
constructor(private authService: NbAuthService,
private http: HttpClient) {
// console.log(this.currentRole);
this.authService.getToken().subscribe((token: NbAuthJWTToken) => {
if (token.isValid()) {
this.currentToken = token;
console.log("Executing token change");
this.setRolActual(0);
}
});
}
protected publishCurrentRol(i: number): void {
console.log("Publishing rol id: ", i); // LOG 1
this._currentRole.next(this.getUserRoles()[i]);
}
setRolActual(i: number) {
this.publishCurrentRol(i);
this.currentIndex = i;
}
}
The following is the component the user has to change the role, and that calls the service with the new role.
#Component({
templateUrl: 'escull-rol.component.html',
styleUrls: ['escull-rol.component.scss'],
})
export class EscullRolComponent {
rols: Array<Rol> = [];
actual: number;
constructor( private rolesService: RolesService,
private route: ActivatedRoute,
private router: Router,
private location: Location ) {
this.rols = this.rolesService.getUserRoles();
this.actual = this.rolesService.getRolActualIndex();
}
buttonRolClicked(index: number) {
this.rolesService.setRolActual(index);
this.router.navigate(['inici']);
// console.log('Boto del rol ' + index + ' clicat');
}
}
And here the header, which changes its state depending on the role:
#Component({
selector: 'ngx-header',
styleUrls: ['./header.component.scss'],
templateUrl: './header.component.html',
})
export class HeaderComponent implements OnInit {
#Input() position = 'normal';
user: any = {};
picture: string;
client: stream.Client;
logoPath = '';
logoEra = '';
rol: string;
ids: Array<string>;
constructor(private sidebarService: NbSidebarService,
/* ... more injections .. */
private imatgesService: ImatgesService,
private notificacionsService: NotificacionsService) {
this.logoEra = 'assets/images/logoEra.png';
this.authService.onTokenChange()
.subscribe((token: NbAuthJWTToken) => {
if (token.isValid()) {
if (token.getValue()) {
this.user = token.getPayload(); // Posem les dades del token a l'objecte user
// console.log('token changed, changing user in header', token);
}
}
}, error => {
console.error('Error en carregar el token en el header');
throw error;
});
this.rolesService.currentRole.subscribe((rol: Rol) => {
// This subscription here should receive the change from the service
if(rol) {
console.log("Changing rol on header to ", rol.getIndex()); // LOG 2
this.rol = rol.getTitle();
this.ids = rol.getRolIDs();
}
});
this.imatgesService.getProfileImagePath().subscribe((path: string) => {
this.picture = path;
}, (err) => {
this.picture = '';
});
}
}
The behaviour that I'm seeing is, the EscullRol component calling the setRolActual(id) method with the new id, and then the service calling its internal method publishCurrentRole(id) with the same id, so at LOG 1 I can see the expected outoput. But then immediately next I can see the output form LOG 2 at the Header Component with the wrong id, which is always the number that we had initially saved at the localStorage when the app started up.
I don't really know if the issue is with how I use the observables, with the service-component communication model or with how components and observables are initailsed and treated in angular.
Few thing to try
First make your service as a singleton using
#Injectable({ providedIn: "root" })
Improvement
Also, make sure that the service is not provided on child modules, as that would create their own instance and it wouldn't be a singleton anymore. If there are more than one instance of the service, the Subject-Observer pattern will not work across all the app.
Then this code
currentRole = this._currentRole.asObservable();
You should create a function to return the data not defined as an variable like
getCurrentRole() {
return this._currentRole.asObservable();
}
I've an authentication guard that checks results from a BehaviorSubject but, before checking it, I need to check chrome local storage that return values in a callback and if the token is invalid renew it and inform BehaviorSubject to allow specific route.
How can I check local storage in get function?
Follow the code to better understand the flow.
auth.guard.ts
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.authService.isLoggedIn
.take(1)
.map((isLoggedIn: boolean) => {
if (isLoggedIn) {
return true;
}
this.router.navigate(['/login']);
return false;
});
}
}
auth.service.ts
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
// Here I need check Google Storage that return a callback
// The sintax for is chrome.storage.sync.get('keys', callback);
return this.loggedIn.asObservable();
}
}
If this is all you are doing then you don't need to use observables. You aren't doing anything asynchronous. You just want to check the current value of local storage to see if the user is still authenticated.
The CanActivate interface allows you to return Observable<boolean> | Promise<boolean> | boolean. So you could just do something like this:
auth.guard.ts
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
if(this.authService.isLoggedIn) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
auth.service.ts
export class AuthService {
get isLoggedIn() {
return localStorage.getItem('my-auth-key');
}
}
If you need to expose the authentication state as an observable for other reasons in your app then you could set a timer when you put their session in local storage. It could either poll on an acceptable interval, set it to the absolute expiration of the session, or something like that depending on your authentication scheme. The basic concept is that the only way to make your localstorage "observable" is to use a timer or some event.
For one app I worked on we have a token with an absolute expiration that we refresh on its session half-life. When I put it in local storage I set a timer for its absolute expiration. When I refresh the token and put it back in storage I cancel the previous timer. If the refresh fails for an extended period then the timer will eventually fire. It will forcefully remove the session from local storage since it is expired and it will broadcast (ngrx in my case but BehaviorSubject in yours) that the session has expired.
If you want to have a route change trigger an auth check just to be sure then you could do this:
auth.service.ts
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
this.loggedIn.next(localStorage.getItem('my-auth-key'));
return this.loggedIn.asObservable();
}
}
Since it is a behavior subject you can push a new value to it before you return it.
EDIT
Since you mentioned that you have to do chrome.storage.sync.get and it is async you could do the following (I'm not doing anything with parsing whatever comes out of storage):
auth.service.ts
export class AuthService {
private loggedIn = new Subject<boolean>();
get isLoggedIn() {
chrome.storage.sync.get('my-auth-key', (isAuthenticated) => {
this.loggedIn.next(isAuthenticated);
});
return this.loggedIn.asObservable();
}
}
Note that I changed it to just Subject rather than BehaviorSubject. This will force the recipient to wait for the .next call. This assumes that you want to check the authentication state each time someone tries to subscribe.
The more elegant solution would be to subscribe to chrome.storage.onChanged to feed your BehaviorSubject. You would probably set that up in your constructor. Something like this... I have not used this api before so this is just the general idea... you may need to do null checks or do parsing or something... and if the values don't expire from the store then you would still need a timer to remove them to fire the change.
auth.service.ts
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
constructor(){
chrome.storage.onChanged.addListener((changes, namespace) => {
this.loggedIn.next(change['my-auth-key'].newValue);
});
}
get isLoggedIn() {
return this.loggedIn.asObservable();
}
}
You could just create an observable out of chrome storage methods:
export class RxChromeStore {
get(key: string): Observable<any> {
return Observable.create(obs => {
let cb = (err, data) => { // use whatever the chrome storage callback syntax is, this is typical cb structure
if (err) {
obs.error(err);
} else {
obs.next(data);
}
};
chrome.storage.sync.get(key, cb);
}).first();
}
}
export class AuthService {
private loggedIn = new BehaviorSubject<boolean>(false);
get isLoggedIn() {
return Observable.zip(RxChromeStore.get('your-auth-key'), this.loggedIn.asObservable(), (chromeAuth, authSubj) => chromeAuth || authSubj);
}
}
your question is a little vague so I'm not sure what your exact goal is, but the basic point here is that you can always create an observable out of soemthing that is callback based using the create method, then you can treat it like any other observable:
RxChromeStore.get('auth-key').switchMap((auth) => (auth.invalid) ? this.http.get('reauthendpoint') : this.loggedIn.asObservable());
or whatever stream you need
You can use localStorage variable to store/fetch data.
Storing: localStorage.setItem(key,value);
Fetching: localStorage.getItem(key);
This localStorage variable is provided by typescript node_module which you will already been having in your angular project.