nebular icon indicating copy to clipboard operation
nebular copied to clipboard

How to refresh tokens in NbPasswordAuthStrategy

Open cloakedch opened this issue 5 years ago • 16 comments

I would like to know how refreshing tokens with the NbPasswordAuthStrategy is supposed to be implemented on the server side. As there is no refresh-/access-token concept like in the NbOAuth2AuthStrategy, I have the impression that it is only possible to refresh a token using an already expired token. Is that correct? I read that refreshing an expired token has security implications.

cloakedch avatar Jan 08 '19 11:01 cloakedch

Hi!

I also want to implement a refreshing token concept with NbPasswordAuthStrategy but I can't find how I can store and reach refresh token using NbAuthService. If you find the solution could you help me?

Thanks advanced, l.

uracs avatar Jan 22 '19 15:01 uracs

below snippets should do the trick.

NbAuthModule.forRoot({
    strategies: [
      NbPasswordAuthStrategy.setup({
        name: 'email',
        ...
        token: {
          class: NbAuthOAuth2JWTToken,
          key: 'token'
        }
      })
    ]
   ...
   {
    provide: HTTP_INTERCEPTORS,
    useClass: NbAuthJWTInterceptor,
    multi: true
  }
  ...

in server

function login(req, res) {
  //login logic to be added
  const dbUser =loggedInuser
  res.json({
    token: {
      access_token: 'jwt access token here',
      refresh_token: 'refreshtoken here'
  });
}

function refreshToken(req, res) {
  const reqAccessToken = req.body.token.access_token;
  const reqRefreshToken = req.body.token.refresh_token;

  const jwtPayload = undefined; // add logic for decode access token
  const dbUser = undefined; // add logic for db user lookup using jwtPayload.user_id
  // generate new access token
  res.json({
    token: {
      access_token: 'jwt access token here',
      refresh_token: 'same refreshtoken here'
  });
}

sasikumardr avatar Jan 29 '19 04:01 sasikumardr

this would be very helpful functionality

kgrigorian avatar Apr 03 '19 12:04 kgrigorian

@sasikumardr I know this is an old post but I'm curious if this actually works for you? I tried this, but I don't believe the refreshToken strategy is being called from this.authService.isAuthenticatedOrRefresh() in the NbAuthJWTInterceptor interceptor.

I can successfully log in, but when it comes time to refresh my token it seems to loop at the interceptor, and I don't ever see my backend endpoint being hit.

dpcamp avatar Jan 06 '20 17:01 dpcamp

@sasikumardr I know this is an old post but I'm curious if this actually works for you? I tried this, but I don't believe the refreshToken strategy is being called from this.authService.isAuthenticatedOrRefresh() in the NbAuthJWTInterceptor interceptor.

I can successfully log in, but when it comes time to refresh my token it seems to loop at the interceptor, and I don't ever see my backend endpoint being hit.

Just in case anyone else runs into this issue, I needed to have the endpoint for my refresh-token added to the NB_AUTH_TOKEN_INTERCEPTOR_FILTER provider

I had one for my login endpoint, but realized I needed a second one for my refresh endpoint as well

      provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER,
      useValue: function (req: HttpRequest<any>) {
         if (req.url === `${environment.apiUrl}/login`)
         {
         return true
         } 
         if (req.url === `${environment.apiUrl}/refresh-token`)
        {
           return true
         }
         else {
          return false
         }
      },

found this over at https://github.com/akveo/ngx-admin/issues/1375

dpcamp avatar Jan 07 '20 16:01 dpcamp

I still have the same problem.

I tried to implemente refresh-token as @sasikumardr said, but still doesn't work.

If I use the NbAuthJWTToken the app just work fine, but when the token expires, the token-refresh endpoint, is called, issued a new one but it seems it is not stored anywhere.

If I try the NbAuthOAuth2JWTToken implementation, then I cannot even login. even with the code proposed by @dpcamp

EDIT: Debugging I see that AuthGuard has the authenticated parameter set to undefined. So login is working but somehow authguard, is not.

isAuthenticatedOrRefresh() { ... ... return of(token.isValid()); <---------------- token.token == token.payload

EDIT2: For the next one, just keep in mind that you have to return in your server a token object, i've just realised that was returning different thing and not exactly:

{
    token: {
      access_token: 'jwt access token here',
      refresh_token: 'same refreshtoken here'
    }
}

EDIT3: Still not working... I am in the same situation as with NbAuthJWTToken. The new token is not stored or used

EDIT4: Too many hours working on a row :) I can say that everything works perfectly

AllTerrainDeveloper avatar Mar 07 '20 20:03 AllTerrainDeveloper

@AllTerrainDeveloper what was your final solution?

fbarajas-fmg avatar Mar 08 '20 23:03 fbarajas-fmg

Sorry, I can't remember, but it was my fault.

My initial problem was that client was not storing the new token after refresh the token. I changed the NbAuthJWTToken with the OAuth2 one, it now made the client to look into an object called token, with the fields access_token and refresh_token. I was sending from my server side, two token objects, instead of on token object, with the two tokens inside.

But I barely remember, that I had changed the token key to token_access, instead of just token.

Loving so far this nebular framework

AllTerrainDeveloper avatar Mar 09 '20 00:03 AllTerrainDeveloper

I'm trying to make own NbAuthJWTToken class (i add only RefreshToken), but in NbAuthTokenParceler methods wrap and unwrap i'm losing my extra properites.

piotrbrzuska avatar May 29 '20 10:05 piotrbrzuska

Ok, i'm make a workaround. In my AuthStrategy (based on NbAuthStrategy) in methods createToken and refreshToken i'm store and load refresh token from localstorage.

piotrbrzuska avatar May 29 '20 10:05 piotrbrzuska

Does anyone know how the refresh token strategy works for NbOAuth2AuthStrategy? I'm having some problems with it: #2442

aksth avatar Jul 09 '20 14:07 aksth

@AllTerrainDeveloper Hello I am stuck on this exact problem, would you mind explaining how to use the NbOAuth2AuthStrategy to look into the token object? My token is returned from Django Rest Framework and consists of {access: xxxx.yyyy.zzzz, refresh: aaaa.bbbb.cccc}

victor-wyk avatar Aug 10 '20 18:08 victor-wyk

Hi, @victor-wyk Could you please explain how you solved this issue? I faced same problem and couldn't find answer.

forest1206 avatar Feb 19 '21 16:02 forest1206

It is very easy. For Django Rest Framework, we need to use djangorestframework-jwt library. This library will have two urls, token obtain and token refresh, instead of having both token refresh and obtain in the same url. This way you can provide the token format to Nebular's NbJWTAuthStrategy, instead of using OAuth2 which is more complex imho. { token: 'xxxx.yyyy.zzzz.' }

victor-wyk avatar Feb 21 '21 21:02 victor-wyk

Look at my solution for refresh token https://github.com/akveo/nebular/issues/2639#issuecomment-1183414210

SergeNarhi avatar Jul 13 '22 16:07 SergeNarhi

This is my solution with angular 15 and nebular auth 9 app.module.ts

const formSetting: any = {
  redirectDelay: 0,
  showMessages: {
    success: true,
  },
};

export function filterInterceptorRequest(req: HttpRequest<any>) {
  return ['http://localhost:3001/auth/',
  ]
    .some(url => req.url.includes(url));
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    NbSecurityModule.forRoot({
      accessControl: {
        guest: {
          view: ['news', 'comments'],
        },
        user: {
          parent: 'guest',
          create: 'comments',
        },
        moderator: {
          parent: 'user',
          create: 'news',
          remove: '*',
        },
      },
    }),
    NbAuthModule.forRoot({
      strategies: [
        NbPasswordAuthStrategy.setup({
          name: 'email',
          baseEndpoint: 'http://localhost:3001',
          token: {
            class: NbAuthOAuth2JWTToken,
            getter: function (method: any, response: any) {
              return {
                access_token: response.body.access_token,
                refresh_token: response.body.refresh_token,
              };
            },
          },
          refreshToken: {
            endpoint: '/auth/refresh',
            method: 'get',
            requireValidToken: true,
            redirect: {
              success: null,
              failure: null,
            },
            defaultErrors: ['Something went wrong, please try again.'],
            defaultMessages: ['Your token has been successfully refreshed.'],
          },
          login: {
            alwaysFail: false,
            endpoint: '/auth/login',
            method: 'post',
            requireValidToken: true,
            redirect: {
              success: '/home',
              failure: null,
            },
            defaultErrors: ['Email o contraseña incorrectass, inténtelo de nuevo.'],
            defaultMessages: ['Has ingresado exitosamente.'],
          },
          register: {
            endpoint: '/auth/sign-up',
            method: 'post',
          },
          logout: {
            endpoint: '/auth/logout',
            method: 'post',
          },
          requestPass: {
            endpoint: '/auth/request-pass',
            method: 'post',
          },
          resetPass: {
            endpoint: '/auth/reset-pass',
            method: 'post',
          },
        }),
      ],
      forms: {
        login: formSetting,
        register: formSetting,
        requestPassword: formSetting,
        resetPassword: formSetting,
        logout: {
          redirectDelay: 0,
        },
      },
    }),
    BrowserAnimationsModule,
    NbThemeModule.forRoot({name: 'default'}),
    NbLayoutModule,
    NbEvaIconsModule,
  ],
  providers: [AuthGuard,
    {provide: HTTP_INTERCEPTORS, useClass: NbAuthJWTInterceptor, multi: true},
    {provide: HTTP_INTERCEPTORS, useClass: BearerInterceptor, multi: true},
    {provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: filterInterceptorRequest},
    {provide: NbRoleProvider, useClass: CustomRoleInterceptor},
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
} 

Bearer Interceptor

@Injectable()
export class BearerInterceptor implements HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (req.url.includes("/auth/refresh")) {
      const token = JSON.parse(localStorage.getItem('auth_app_token') || '{}');
      console.log(token);
      const refreshToken = JSON.parse(token.value).refresh_token;
      if (refreshToken) {
        const cloned = req.clone({
          headers: req.headers.set('Authorization', 'Bearer ' + refreshToken),
        });
        return next.handle(cloned);
      } else {
        return next.handle(req);
      }
    } else {
      return next.handle(req);
    }
  }
} 

Bearer interceptor is necesary if you use in backend header for send refreshToken, if you use post nebular Auth send payload for refresh Token.

andressan95 avatar Jun 16 '23 19:06 andressan95