Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 7050

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 60k
Author
Asked: November 28, 20242024-11-28T12:13:11+00:00 2024-11-28T12:13:11+00:00

Authentication in Angular: Part III

  • 60k

Today let me add a proper logout, and before we move forward to more features, let's clean up and apply some of the lessons we learned before.

Follow along on StackBlitz

Logout, in the right places

Looking back at our Http interceptor, a 401 might happen more than once, signaling a bad refresh token. When that happens we can either toast the user about a dramatic failure, and give them the chance to login again, or we can force a redirect to the login page ourselves. May the force be with us.

The solution is to reroute in the Logout function in AuthState. We'll make it optional because the Logout function is called when a user fails to login, and when the AuthState returns an invalid access token on a page that does not necessarily need authentication.

// services/auth.state  // reroute optionally Logout(reroute: boolean = false) {   // remove leftover   this.RemoveState();   // and clean localstroage   localStorage.removeItem('user');    if (reroute) {     this.router.navigateByUrl('/public/login');   } } 
Enter fullscreen mode Exit fullscreen mode

In the AuthState constructor, do not reroute. If we do, we needlessly reroute users in safe routes. When it fails after a refresh token, that is a good place to redirect. With a but.

// http return this.authService.RefreshToken()   .pipe(      switchMap((result: boolean) => {         if (result) {           //...         }      }),      catchError(error => {         // exeption or simply bad refresh token, logout an reroute         this.authState.Logout(true);         return throwError(() => error);      }),     // ...   ); 
Enter fullscreen mode Exit fullscreen mode

Another location we want to logout and reroute is when the user clicks the Logout button intentionally.

// app.component Logout() {   // logout and reroute   this.authState.Logout(true); } 
Enter fullscreen mode Exit fullscreen mode

What if we are on a safe route already?

There is a scenario where the API call needs user authentication but the page it displays itself, has a public version of it. For example, if you route to a public Twitter account while you have your own login, you can see the like button and use it. If for some reason the refresh token is no longer valid, then clicking on the like button, should somehow warn users of the lack of authentication, and it should ask the user if they want to login again. In situations like that, having a global redirect in the Http interceptor is not ideal. The solution is contextual. Some API calls need to redirect, and some need to toast only. Here is an example of a like button click.

// example this.tweetService.CreateLike(params).pipe(    catchError(error => HandleSpecificError(error)) );  // somewhere in our common functions, or toast service HandleSpecificError(error: any): Observable<any> {     // if error is of http response 401, show a toast with a button to relogin   if (error instanceof HttpErrorResponse && error.status === 401) {     ShowToastWithLogin();     return of(null);   } else {     // handle differently or rethrow     return throwError(() => error);   } } 
Enter fullscreen mode Exit fullscreen mode

I would choose which way to go according to the type of project I am developing. There is no silver bullet. (You may use HttpContext token for that.)

We wrote about toast messages in Angular previously.

Clean up

Before we move on I would like to clean up the AuthState to allow extra properties. We are going to still use this service to manipulate the localStorage of the browser. You might be tempted to create a new service for that, but I see no great value because we only need private members for the authenticated user information. Accessing the localStorag directly is not ideal, but let's keep going.

// authState update, add all necessary functions to deal with localStorage  private _SaveUser(user: IAuthInfo) {   localStorage.setItem(    ConfigService.Config.Auth.userAccessKey,    JSON.stringify(user)   ); } private _RemoveUser() {   localStorage.removeItem(ConfigService.Config.Auth.userAccessKey); }  private _GetUser(): IAuthInfo | null {   const _localuser: IAuthInfo = JSON.parse(     localStorage.getItem(ConfigService.Config.Auth.userAccessKey)   );   if (_localuser && _localuser.accessToken) {     return <IAuthInfo>_localuser;   }   return null; } 
Enter fullscreen mode Exit fullscreen mode

Then we are going to tidy up the other methods to use these. We need a way to SaveSession, and UpdateSession. We also are going to write the logic for CheckAuth. Let's first go back to the IAuthInfo and talk about the expiresAt property,

Expires At

When the information comes back from the authorization server, it usually has a lifetime, rather than an exact date. This is easier to manage given different time zones on server and client. In the client, however, we need to determine the exact time it expires at. A pretty close one, and good enough for our use.

So the expected return model is:

// return from server upon login {     accessToken: 'access_token',     refreshToken: "refres_token",     payload: {         name: 'maybe name',         id: 'id',         email: 'username'     },     // expires in is an absolute lifetime in seconds     expiresIn: 3600 } 
Enter fullscreen mode Exit fullscreen mode

In our model, it's time to properly map to our internal model

// in auth.model we need to properly map the expires at export const NewAuthInfo = (data: any): IAuthInfo => {   return {     payload: {       email: data.payload.email,       name: data.payload.name,       id: data.payload.id,     },     accessToken: data.accessToken,     refreshToken: data.refreshToken,     // map expiresIn value to exact time stamp     expiresAt: Date.now() + data.expiresIn * 1000,   }; }; 
Enter fullscreen mode Exit fullscreen mode

And now in our Login in AuthService we properly map

// services/auth.service Login(username: string, password: string): Observable<any> {   return this.http.post(this._loginUrl, { username, password }).pipe(     map((response) => {       // use our mapper       const retUser: IAuthInfo = NewAuthInfo((<any>response).data);       // ...     })   ); } 
Enter fullscreen mode Exit fullscreen mode

Now checking the authentication whenever we need, is a simple extra layer of precaution. We already send API calls with null tokens, and let the server handle it. So it is no harm to remove the token from localStorage whenever the browser thinks it's invalid.

// services/auth.state write the CheckAuth CheckAuth(user: IAuthInfo) {     // if no user, or no accessToken, something terrible must have happened     if (!user || !user.accessToken) {       return false;     }     // if now is larger than expiresAt, it expired     if (Date.now() > user.expiresAt) {       return false;     }      return true; } 
Enter fullscreen mode Exit fullscreen mode

Saving and updating session

The client-side simple solution is already in place, I call it session even though it is not really a session. This understanding will help us later figure out what to do when we implement SSR.

// services/auth.state service // add two methods: SaveSession and UpdateSession // new saveSessions method SaveSession(user: IAuthInfo): IAuthInfo | null {   if (user.accessToken) {     this._SaveUser(user);     this.SetState(user);     return user;   } else {     // remove token from user     this._RemoveUser();     this.RemoveState();     return null;   } }  UpdateSession(user: IAuthInfo) {     const _localuser: IAuthInfo = this._GetUser();     if (_localuser) {       // only set accesstoken and refreshtoken       _localuser.accessToken = user.accessToken;       _localuser.refreshToken = user.refreshToken;        this._SaveUser(_localuser);       // this is a new function to clone and update current value       // we will move these into their own state class later       this.UpdateState(user);     } else {       // remove token from user       this._RemoveUser();       this.RemoveState();     }   } 
Enter fullscreen mode Exit fullscreen mode

Notice how the UpdateSession is a tad bit different. After a RefreshToken request, we do not need much information from the server, and some servers do not return the payload with it. So it is a good practice to read only the new tokens. To use those two methods:

// services/auth.service // login method Login(username: string, password: string): Observable<any> {   return this.http.post(this._loginUrl, { username, password }).pipe(     map((response) => {       // ... return after savi       return this.authState.SaveSession(retUser);     })   ); }  RefreshToken(): Observable<boolean> {   return (     this.http       // FIX: get refresh token, not token       .post(this._refreshUrl, { token: this.authState.GetRefreshToken() })       .pipe(         map((response) => {            if (!response) {             throw new Error('Oh oh');           }            // map first, then update session           const retUser: IAuthInfo = NewAuthInfo((<any>response).data);           this.authState.UpdateSession(retUser);            return true;         })       )   ); } 
Enter fullscreen mode Exit fullscreen mode

We can also make use of our new private methods in the constructor of AuthState and Logout

// services/auth.state constructor(private router: Router) {       // use our new _GetUser     const _localuser: IAuthInfo = this._GetUser();      if (this.CheckAuth(_localuser)) {       this.SetState(_localuser);     } else {       this.Logout(false);     }   } // ... Logout(reroute: boolean = false) {   // use our new _RemoveUser   this._RemoveUser();     //... } 
Enter fullscreen mode Exit fullscreen mode

Now we're ready for a redirect URL. Let this all sink in first, we'll do that next episode. 😴

RELATED POSTS

Catching and displaying UI errors with toast messages in Angular

garage.sekrab.com

angularauthtutorialwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 1k
  • Popular
  • Answers
  • Author

    How to ensure that all the routes on my Symfony ...

    • 0 Answers
  • Author

    Insights into Forms in Flask

    • 0 Answers
  • Author

    Kick Start Your Next Project With Holo Theme

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.