add refresh token

This commit is contained in:
Roland Schneider 2025-11-14 15:50:19 +01:00
parent 42158d1fd4
commit 96af8e564b
6 changed files with 26 additions and 16 deletions

View File

@ -17,12 +17,20 @@ export class App {
constructor(private authService: AuthService, private router: Router) {} constructor(private authService: AuthService, private router: Router) {}
logout(): void { logout(): void {
// Make a best-effort to log out on the server, but always // Make a best-effort to log out on the server.
// clean up the client-side session in the `finalize` block. // The client-side logout will run regardless of whether this call
this.authService.serverSideLogout().pipe( // succeeds or fails.
finalize(() => { this.authService.serverSideLogout().subscribe({
// The server call can succeed or fail, we don't care about the result,
// we just want to ensure the client is logged out.
next: () => {
console.info("server logged out")
this.authService.clientSideLogout(); this.authService.clientSideLogout();
}) },
).subscribe(); error: () => {
console.error("server failed to log out")
this.authService.clientSideLogout();
},
});
} }
} }

View File

@ -32,6 +32,7 @@ export class AuthService {
* This is the definitive logout action from the user's perspective. * This is the definitive logout action from the user's perspective.
*/ */
clientSideLogout(): void { clientSideLogout(): void {
console.info("clientSideLogout")
this.removeTokens(); this.removeTokens();
this.router.navigate(['/login']); this.router.navigate(['/login']);
} }

View File

@ -53,10 +53,9 @@ export class JwtInterceptor implements HttpInterceptor {
this.isRefreshing = false; this.isRefreshing = false;
this.refreshTokenSubject.error(err); this.refreshTokenSubject.error(err);
// In a refresh failure, the user MUST be logged out. // The interceptor's job is done. It failed to refresh.
// Call the synchronous client-side logout to avoid re-intercepting. // It should NOT handle logout. It should just propagate the error.
this.authService.clientSideLogout(); // The calling service/component will be responsible for the user-facing action.
return throwError(() => err); return throwError(() => err);
}) })
); );

View File

@ -6,11 +6,11 @@ import {
UseGuards, UseGuards,
Req, Req,
} from '@nestjs/common'; } from '@nestjs/common';
import AuthService from './auth.service';
import { LoginRequestDto } from './dto/login-request.dto'; import { LoginRequestDto } from './dto/login-request.dto';
import { JwtAuthGuard } from './jwt-auth.guard'; import { JwtAuthGuard } from './jwt-auth.guard';
import { JwtRefreshAuthGuard } from './jwt-refresh-auth.guard'; import { JwtRefreshAuthGuard } from './jwt-refresh-auth.guard';
import express from 'express'; import express from 'express';
import { AuthService } from './auth.service';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {

View File

@ -2,23 +2,27 @@ import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { UserModule } from '../user/user.module'; import { UserModule } from '../user/user.module';
import AuthService from './auth.service'; import { AuthService } from './auth.service';
import { AuthController } from './auth.controller'; import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy'; import { JwtStrategy } from './jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtRefreshTokenStrategy } from './jwt-refresh.strategy'; import { JwtRefreshTokenStrategy } from './jwt-refresh.strategy';
import { StringValue } from 'ms';
@Module({ @Module({
imports: [ imports: [
ConfigModule, ConfigModule,
UserModule, UserModule,
PassportModule, PassportModule,
// Restore the correct async registration for JwtModule
JwtModule.registerAsync({ JwtModule.registerAsync({
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],
useFactory: (configService: ConfigService) => ({ useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'), secret: configService.get<string>('JWT_SECRET'),
signOptions: { expiresIn: '2m' }, signOptions: {
expiresIn: configService.get<StringValue>('JWT_EXPIRATION_TIME'),
},
}), }),
}), }),
], ],

View File

@ -8,7 +8,7 @@ import { ConfigService } from '@nestjs/config';
import type { StringValue } from 'ms'; import type { StringValue } from 'ms';
@Injectable() @Injectable()
class AuthService { export class AuthService {
constructor( constructor(
private userService: UserService, private userService: UserService,
private jwtService: JwtService, private jwtService: JwtService,
@ -109,5 +109,3 @@ class AuthService {
}; };
} }
} }
export default AuthService;