I implemented a login in angular.
It works well, but I like to redirect after successfully authentication.
The redirect does not work after receiving the user from server.
If I refresh my page and use the login, it works.
I receive an object from my webservice and save it to "localStorage".
I use a BehaviourSubject to observe my current user.
If I press 'login' i run into auth guard, but the auth guard does not know the user. (its null)
What is my mistake?
Auth.guard.ts
#Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private authenticationService: AuthService
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log(this.authenticationService.currentUserValue);
if (this.authenticationService.currentUserValue) {
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/entrance/login'], { queryParams: { returnUrl: state.url } });
return false;
}
}
#Injectable({
providedIn: 'root'
})
export class AuthService {
private currentUserSubject: BehaviorSubject<User>;
constructor(private http: HttpClient) {
this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
}
public get currentUserValue(): User {
return this.currentUserSubject.value;
}
login(username: string, password: string) {
return this.http.post<User>('/api/auth', { username, password }).pipe(map(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
this.currentUserSubject.next(user);
}
return user;
}));
}
this.authService.login(email, password).pipe(first()).subscribe(data => {
this.router.navigate([this.returnUrl]);
});
I like to redirect directly after login without refresh.
You see any mistakes?
Related
I am using firebase phone-authentication in my ionic 4 app. I have successfully implemented jwt authentication. If the token is available I redirect users to the main page else redirecting to the login page.
But here comes the problem on the login page. If the user is new(coming for the first time) then I want to redirect to the details page to get some other data like email, name, else (old user) redirect to the main page.
The problem is I don't have different buttons for login and signup in my app. The same login is used for both login and register
What is the best solution to check if the user is new or old? The last solution is to check the user in the database and redirect accordingly.
Here is my code
authentication.service.ts
authenticationState = new BehaviorSubject(false);
constructor(private storage: StorageService, private plt: Platform, private router: Router) {
this.plt.ready().then(() => {
this.checkToken();
});
}
checkToken() {
this.storage.get(TOKEN_KEY).then(res => {
if (res) {
this.authenticationState.next(true);
}
})
}
login() {
return this.storage.set(TOKEN_KEY, 'Bearer 1234567').then(() => {
this.authenticationState.next(true);
});
}
logout() {
return this.storage.clear().then(() => {
this.authenticationState.next(false);
this.router.navigate(['home'])
});
}
isAuthenticated() {
return this.authenticationState.value;
}
app.component.ts
constructor(private authenticationService: AuthenticationService){
this.authenticationService.authenticationState.subscribe(state => {
if (state) {
this.router.navigate(['menu/items']);
} else {
this.router.navigate(['home']);
}
});
}
home.page.ts
if(old user) {
// redirect to main page
}
else {
redirect to details page
}
I need to show a login modal every time the server returns a http unauthorized status (401), and in that case, stop the page loading... for example, I'm logged in but trying to access an protected resource that only admin users can do it.. so in that case I would like to show an modal with login and password to the user. It could be on navigating to a protected route or on delete event for example.
I tried to do it in an ApiInterceptor:
#Injectable({providedIn: 'root'})
export class ApiInterceptor implements HttpInterceptor {
constructor(
...
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({ url: environment.baseUrl + req.url });
if (this.authService.validToken) {
req = req.clone({ headers: req.headers.set('Authorization', `Bearer ${this.authService.validToken}`) });
}
if (!req.headers.has('Content-Type')) {
req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
}
return next.handle(req).pipe(catchError(resp => this.handleError(resp)));
}
private handleError(httpError: HttpErrorResponse) {
if (httpError.status === this.UNAUTHORIZED) {
// opening login modal here, but can't stop the request to prevent user to se unauthorized data, and after login, how can I redirect user to the same resource he tried to access?
}
return throwError(httpError);
}
}
Need help here, if someone have an idea in how to do it will be appreciated!
Your ApiInterceptor looks like it's for adding a bearer token to the request. I'd call this the TokenInterceptor or similar, and create a new one for handling unauthorised requests.
I'd create a new interceptor and call this UnauthorisedRequestInterceptor. Something similar to this:
#Injectable({ providedIn: 'root' })
export class UnauthorisedRequestInterceptor implements HttpInterceptor {
constructor(private router: Router) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
map(event => {
return event;
}),
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.router.navigate(['/auth/login']);
}
return throwError(error);
})
);
}
}
This will intercept every http request, and if the returned status is 401, it will redirect you to your login page.
Then add this into your list of providers in app.module.ts:
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UnauthorisedRequestInterceptor,
multi: true
}
]
As for redirecting users on access to a protected route, this should be done in your auth guard.
Every protected data that need roles or permissions should be on the server & have specific authorization to access it, if there is already protected data on your application you should consider moving it onto your server or add at least a guard.
If you want to redirect the user after a 401 you have to inject the Router service in your interceptor & use the navigate function (cf this.router.navigate(['/myRoute']); )
My auth is based on 2 things :
firebase auth (email/password)
call on a server API to retrieve full customer entity from BDD and from firebaseID (user must exists)
So a user will be "authenticated" if these two conditions are met.
I also have authGuards based on a isAuthenticated() returning an Observable (because on a page refresh, guard must wait for the auth to be finished before redirecting the user anywhere).
Problem : I can't find a way to make that work with all the async and rxjs mess/hell .. Currently it's working but each time isAuthenticated is called, the serverAPI auth is called every time...
How can I refactor that in order to call server only once and all the async/reload stuff still works ?
AuthService :
export class AuthService {
public userRole: UserBoRole;
public authState$: Observable<firebase.User>;
constructor(
private afAuth: AngularFireAuth,
private snackBar: SnackBarService,
private translate: TranslateService,
private router: Router,
private grpcService: GrpcService
) {
this.authState$ = this.afAuth.authState.pipe(
take(1),
mergeMap(user => {
if (!user) {
return of(user);
}
// User is successfully logged in,
// now we need to check if he has a correct role to access our app
// if an error occured, consider our user has not logged in, so we return null
return this.checkProfile().pipe(
take(1),
map(() => {
this.test = true;
return user;
}),
catchError(err => {
console.error(err);
return of(null);
})
);
})
);
// Subscribing to auth state change. (useless here because access logic is handled by the AuthGuard)
this.authState$.subscribe(user => {
console.log('authState$ changed :', user ? user.toJSON() : 'not logged in');
});
}
checkProfile() {
return this.callAuthApi().pipe(
map((customer) => {
if (!customer || customer.hasRole() === "anonymous") {
return Promise.reject(new Error(AuthService.AUTH_ERROR_ROLE));
}
this.userRole = customer.getRole();
})
);
}
isAuthenticated(): Observable<boolean> {
return this.authState$.pipe(map(authState => !!authState));
}
}
AuthGuard :
export class AuthGuard implements CanActivate, CanActivateChild {
constructor(private authService: AuthService, private router: Router) {}
check(): Observable<boolean> {
return this.authService.isAuthenticated().pipe(
catchError(err => {
// notifying UI of the error
this.authService.handleAuthError(err);
// signout user
this.authService.signOut();
// if an error occured, consider our user has not logged in
return of(false);
}),
tap(isAuthenticated => {
if (!isAuthenticated) {
// redirecting to login
this.router.navigate(['login']);
}
})
);
}
canActivateChild(): Observable<boolean> {
return this.check();
}
canActivate(): Observable<boolean> {
return this.check();
}
}
Thanks
You can change your checkProfile() function to return observable instead of observable from http request or promise in case of error. First you will check if the user already authenticated(I assumed that userRole will be fine since you save it after call to back end) and if yes return a newly created observable without call to your back end, otherwise you will make a request and emit your observable based on result of http call. With next example you will make call only once:
checkProfile() {
return new Observable((observer) => {
if (this.userRole) {
observer.next();
observer.complete();
} else {
this.callAuthApi().pipe(
map((customer) => {
if (!customer || customer.hasRole() === "anonymous") {
observer.error(new Error(AuthService.AUTH_ERROR_ROLE));
observer.complete();
}
this.userRole = customer.getRole();
observer.next();
observer.complete();
})
);
}
});
}
Haha, ReactiveX is not easy one. It has a quite steep learning curve.
But it is really powerful.
1. call server only once
You can use shareReplay.
To understand how shareReplay works, have a look here https://ng-rxjs-share-replay.stackblitz.io
//shareReplay example
ngOnInit() {
const tods$ = this.getTodos();
tods$.subscribe(console.log);// 1st sub
tods$.subscribe(console.log);// 2st sub
}
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.url)
.pipe(
tap(() => console.log('Request')),
shareReplay(1) // compare with comment and uncomment
);
}
Output with shareReplay
Request
[Object, Object, Object]
[Object, Object, Object]
Output without shareReplay
Request
[Object, Object, Object]
Request
[Object, Object, Object]
You may use shareReplay in your auth service code.
//auth.services.ts
import { shareReplay } from 'rxjs/operators';
...
this.user$ = this.afAuth.authState.pipe(
tap(user => {
console.log('login user$ here', user)
}),
switchMap(user => {
if (user) {
//do something
return this.db.object(`users/${user.uid}`).valueChanges();
} else {
return of(null);
}
}),
shareReplay(1) //**** this will prevent unnecessary request****
);
2. async and await
toPromise()
//auth.service.ts
...
getUser() {
return this.user$.pipe(first()).toPromise();
}
//auth.guard.ts
...
async canActivate(next: ActivatedRouteSnapshot
, state: RouterStateSnapshot
): Promise<boolean> {
const user = await this.auth.getUser();
//TODO your API code or other conditional authentication here
if (!user) {
this.router.navigate(['/login']);
}
return !!user;
}
Hope this will help you.
I have a login component which when a user logs in, the current User details is stored in a local Storage variable. So when login is successful will store the current User details in a local Storage Variable and navigate to dashboard, i have used the local Storage variable to get the user details and display in Header of webpage, but view is not changing once i refresh the page i will..
login() {
this.authenticationService.login(this.model.username,
this.model.password)
.subscribe(
data => {
this.activeModal.dismiss();
this.router.navigate(['dashboard']);
},
error => {
this.alertService.error(error);
});
}
In Service
login(username: string, password: string) {
return this.http.post('/api/authenticate', JSON.stringify({ username:
username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
let user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep
user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
});
}
getCurrentUser() {
return JSON.parse(localStorage.getItem('currentUser'));
}
In Navigation Component
constructor(private authenticationService: AuthenticationService) {
getUser() {
this.currentUser = this.authenticationService.getCurrentUser();
}
}
View
<li *ngIf="currentUser">Welcome {{currentUser.firstName}}
</li>
<li *ngIf="currentUser">Logout</li>
<li *ngIf="!currentUser">Login
</li>
<li *ngIf="!currentUser">Register</li>
Solution I found is to use DoCheck lifecycle hook.
ngDoCheck gets called to check the changes in the directives in addition to the default algorithm
ngDoCheck() {
this.getUser();
}
I´m triying to implement facebook authenticathion from my angular 2 app but I think I'm implenting in the wrong way and I can't find some tutorials about this and also I want to ask if ¿is Auth0 the only way to implement this?.
Well I have this:
Node JS server :
// FB authentication
authRoutes.get('/facebook', passport.authenticate('facebook', { session : false, scope: ['email'] }))
authRoutes.get('/facebook/callback', passport.authenticate('facebook', { session : false, failureRedirect: '/'}), ctrlAuthentication.facebookResponse)
these are the routes in the api and I'm using passportjs, all works when I call the api directly in the browser like: localhost:3000/api/auth/facebook and I get the user returned from the api in JSON format.
Now in the front I have angular 2 and this is
authentication.service.ts:
import {User} from '../_models/user'
#Injectable()
export class AuthenticationService {
public token : String;
public user : User;
constructor(private http: Http){
}
fbLogin() : Observable<User> {
let user = this.http.get('/api/auth/facebook')
.map(this.mapUser);
return user;
}
mapUser(response : Response) : User {
console.log(response)
let data = response.json();
this.token = data.token;
let user = <User>(data.user);
this.user = user;
localStorage.setItem('currentUser', JSON.stringify({ user: user, token: data.token }));
return user;
}
}
and this is the register.component.ts:
import {AuthenticationService} from '../_services/authentication.service'
import {User} from '../_models/user'
#Component({
selector: 'crowd-register',
templateUrl: 'app/RegisterComponent/register.component.html'
})
export class RegisterComponent {
private user : User;
constructor(
private authenticationService : AuthenticationService
) {}
fbLogin() {
this.authenticationService.fbLogin().subscribe(
user => {
this.user = user;
}
)
}
}
When I call fbLogin() from the app I get this error:
SyntaxError: Unexpected token < in JSON at position 0
MapSubscriber.AuthenticationService.mapUser [as project] (authentication.service.ts:46)
I think this is because I'm don't getting the user from my api, I'm getting the response from facebook and this isn't executing or something.
This is the picture of the console.log(response) and the error.
Thanks for all answers, sorry for my english.