ueberauth / guardian

Elixir Authentication
MIT License
3.43k stars 382 forks source link

NodeJS JWT Compatibility #598

Closed ryanpager closed 5 years ago

ryanpager commented 5 years ago

I am building out a version 2 of our internal API in elixir (phoenix). The guardian module works great, but when I try to reuse an jwt token generated by the nodejs version (using the package jwt), it fails to verify on the version 2 (elixir).

Is there some sort of issue with compatibility? They are using the same hashing algorithm, and secrets -- so I would think it would work.

Code looks like this for the Guardian setup,

defmodule App.Auth.Pipeline do
  use Guardian.Plug.Pipeline,
    otp_app: :api,
    error_handler: App.Auth.ErrorHandler,
    module: App.Auth.Guardian

  plug Guardian.Plug.VerifyHeader, realm: "Bearer"
  plug Guardian.Plug.EnsureAuthenticated
  plug Guardian.Plug.LoadResource
end
yordis commented 5 years ago

Would be great if you can put a simple demo project with Node and Elixir CLIs that just validate the same JWT token.

This will help you to mitigate where the issue is happening and will allow us to investigate this faster.

ryanpager commented 5 years ago

Heres a snippet from out auth controller on the version 1 of the api (node).

The v2 is just using the standard guardian setup -- nothing special at all. The salts for both v1 and v2 are the exact same (I just double checked that again).

  import * as jwt from 'jsonwebtoken'

  @Post('/')
  @ValidateRequest(Joi.object({
    email_address: Joi.string().email().max(255).required(),
    password: Joi.string().max(255).required(),
    timeout: Joi.number().min(1).max(60 * 24 * 365).default(60),
  }))
  public async authenticate(
    @Required() @BodyParams('email_address') email_address: string,
    @Required() @BodyParams('password') password: string,
    @BodyParams('timeout') timeout: number,
    @Request() req: Express.Request
  ): Promise<string> {
    const user = await models.User.findOne({ where: { email_address: req.params.email_address } });
    if (!user) {
      throw new NotFound('The account with the email address supplied does not exist.');
    }

    // if the user is blocked then we need to throw the specific error
    if (user.status === UserStatus.Blocked) {
      throw new Forbidden('This account has been blocked. Please contact support.');
    }

    // encrypt the password on creation for security reasons.
    const passwordsMatch = bcrypt.compareSync(req.params.password, user.password);
    if (!passwordsMatch) {
      throw new BadRequest('The username and/or password supplied is invalid.');
    }

    const sessionDuration = moment.duration(req.params.timeout, 'minutes');
    const token = jwt.sign({
      session: {
        user: {
          id: user.id,
        },
      },
    }, config.JWT_SALT, { expiresIn: sessionDuration.asSeconds() });

    return token;
  }
ryanpager commented 5 years ago

Heres a sample token issued from v1 that fails verification with Guardian:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uIjp7InVzZXIiOnsiaWQiOjEyN319LCJpYXQiOjE1NjI5MDExNTUsImV4cCI6MTU5NDQzNzE1NX0.jzz3G7Zscr9hLrWmyGGdigsOWwmvXpBXkwdo4-bkkdw
ryanpager commented 5 years ago

Any ideas on this -- Im completely stumped here; and its blocking a bunch of implementation. When I use Joken it can decode it with no issues -- but just out of the box guardian says the token is invalid.

ryanpager commented 5 years ago

Nevermind -- figured it out on a whim. For those that were struggling with this -- for some reason you must use the following config settings if your working with the nodejs jsonwebtoken package:

allowed_algos: ["HS256"]
verify_module: Guardian.JWT