Error on validating JWT token IDX12741. JWT: must have three segments (JWS) or five segments (JWE)
Asked Answered
K

2

6

I'm trying to implement token refresh feature in angular 12 and .net core 5.

this is my JWT service registration:

startup.cs:

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;

        }).AddJwtBearer(options =>
        {
            options.SaveToken = true;
            options.RequireHttpsMetadata = false;
            options.TokenValidationParameters = new TokenValidationParameters()
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ClockSkew = TimeSpan.Zero,

                ValidAudience = _conf["JWT:ValidAudience"],
                ValidIssuer = _conf["JWT:ValidIssuer"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_conf["JWT:Secret"]))
            };
        });

And this is my appsettings.json:

"JWT": {
"ValidAudience": "http://localhost:4200",
"ValidIssuer": "http://localhost:4200",
"Secret": "JWTRefreshTokenHIGHsecuredPasswordVVVp1OH7Xzyr",
"TokenValidityInMinutes": 1,
"RefreshTokenValidityInDays": 7
}

after calling login controller a simple access token and a refresh token is sent back to client:

LoginController:

            //login codes omitted for simplicity
            foreach (var role in userRoles)
            {
                authClaims.Add(new Claim(ClaimTypes.Role, role));
            }

            var accessToken = CreateToken(authClaims);
            var refreshToken = GenerateRefreshToken();

            _ = int.TryParse(_configuration["JWT:RefreshTokenValidityInDays"], out int refreshTokenValidityInDays);

            user.RefreshToken = refreshToken;
            user.RefreshTokenExpiryTime = DateTime.Now.AddDays(refreshTokenValidityInDays);

CreateToken Method :

    private JwtSecurityToken CreateToken(List<Claim> authClaims)
    {
        var authSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
        _ = int.TryParse(_configuration["JWT:TokenValidityInMinutes"], out int TokenValidityInMinutes);

        var expires = DateTime.Now.ToLocalTime().AddMinutes(TokenValidityInMinutes);
        var token = new JwtSecurityToken(
            issuer: _configuration["JWT:ValidIssuer"],
            audience: _configuration["JWT:ValidAudience"],
            claims: authClaims,
            expires: expires,
            signingCredentials: new SigningCredentials(authSecurityKey, SecurityAlgorithms.HmacSha256)
            ) ;
        return token;
    }

Now after logging in i have access token and refresh token saved in browser SessionStorage.

Here on client side I've implemented a HTTP INTERCEPTOR to handle 401 unauthorized error. I want to refresh my JWT token when my backend says that token is expired.

auth.interceptor.ts:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  private isRefreshing = false;

  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  constructor(private tokenService: tokenStorageService, private authService: AuthenticationService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<Object>> {
    debugger;
    let authReq = req;
    const token = this.tokenService.getToken();

    if (token != null) {
      authReq = this.addTokenHeader(req, token);
    }
    return next.handle(authReq).pipe(catchError(error => {
      if (error instanceof HttpErrorResponse && !authReq.url.includes('auth/login') && error.status === 401) {
        debugger;
        return this.handle401Error(authReq, next);
      }
      return throwError(error);
    }));
  }
  private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);
      const token = this.tokenService.getRefreshToken();
      const refreshToken=this.tokenService.getRefreshToken();
      if (token)
        return this.authService.refreshToken(token,refreshToken).pipe(
          switchMap((token: any) => {
            this.isRefreshing = false;
            this.tokenService.saveToken(token.accessToken);
            this.refreshTokenSubject.next(token.accessToken);

            return next.handle(this.addTokenHeader(request, token.accessToken));
          }),
          catchError((err) => {
            debugger;
            this.isRefreshing = false;

            //this.tokenService.signOut();
            return throwError(err);
          })
        );
    }
    return this.refreshTokenSubject.pipe(
      filter(token => token !== null),
      take(1)        switchMap((token) => next.handle(this.addTokenHeader(request, token)))
    );
  }
  private addTokenHeader(request: HttpRequest<any>, token: string) {

    return  request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });

  }
}

one minute after login my token gets expired and interceptor calls my refresh-token api.

RefreshToken Controller:

    public async Task<object> RefreshToken(tokenModel tokenModel)
    {
        if (tokenModel == null)
            throw new ServiceException("Invalid Token Model");

        string accessToken = tokenModel.accessToken;
        string refreshToken = tokenModel.refreshToken;

        var principal = GetPrincipalsFromExpiredToken(accessToken);
        if(principal==null)
        {
            throw new  ServiceException("Invalid access token or refresh token");
        }

        string username = principal.Identity.Name;

        var user = await _userManager.FindByNameAsync(username);
        if(user==null || user.RefreshToken!=refreshToken || user.RefreshTokenExpiryTime<=DateTime.Now)
        {
            throw new ServiceException("Invalid access token or refresh token");
        }

        var newAccessToken = CreateToken(principal.Claims.ToList());
        var newRefreshToken = GenerateRefreshToken();
        user.RefreshToken = newRefreshToken;

        await _userManager.UpdateAsync(user);

        return new
        {
            accessToken=new JwtSecurityTokenHandler().WriteToken(newAccessToken),
            refreshToken=newRefreshToken
        };
    }

and this is GetPrincipalsFromExpiredToken method:

private ClaimsPrincipal GetPrincipalsFromExpiredToken(string token)
    {
        var tokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"])),
            ValidateLifetime = false
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        SecurityToken securityToken = null;

        var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);

        if (securityToken is not JwtSecurityToken jwtSecurityToken ||
        !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
        StringComparison.InvariantCultureIgnoreCase))
            throw new SecurityTokenException("Invalid token");

        return principal;

    }

on tokenHandler.ValidateToken I get this error saying:

IDX12741: JWT: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' must have three segments (JWS) or five segments (JWE).

what wrong am I doing? is there a simpler way of implementing jwt refresh token?

this is the generated token:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdEBrYXNiaW0uaXIiLCJqdGkiOiJjM2I2ZTQyZi00ZGI1LTQzMDMtYjY4Mi02YWU5Yzg3ZjI1ZTUiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJVc2VyIiwiZXhwIjoxNjUyMzc0ODI4LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjQyMDAifQ.8rzkMkVENPAyWV2DpPhUUAzza0cXY_HiUpWq2u_Sqqs

Kwangchowan answered 11/5, 2022 at 19:15 Comment(0)
R
2

what does your jwt look like? JWT is consist of three parts:header,playload and signature, and they are splited by"." in your jwt string.Your error was caused by the wrong structure of jwt,I suppose. and you could see the test result as below: enter image description here

Updated

The error message of the second request(I deleted a"." in the jwt): enter image description here

Roturier answered 12/5, 2022 at 2:58 Comment(3)
It hides exception message. I can't see the exception. but I send an expired token to be validated and I think I get the LifeTimeValidateException just like your codeKwangchowan
I updated the exception message of the second request.And it may help If you could check your jwt when you debugRoturier
thank you for your answer. I was sending the wrong tokenKwangchowan
H
1

Your JWT token should have 3 parts separated by dots(.)

To validate, if your JWT token is valid or not, use https://jwt.io/

If your token is valid, it should be shown as Signature Verified as in image. If that is correct, then there is no issue in your token. otherwise you must be sending some wrong value.

enter image description here

Hardness answered 17/11, 2023 at 23:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.