loopback-next
loopback-next copied to clipboard
User authentication-jwt with MySQL
Steps to reproduce
Follow the authentication tutorial below: https://loopback.io/doc/en/lb4/Authentication-tutorial.html
Current Behavior
The authentication-jwt package includes the User model but it's using a strict mode "false" and has, for example, an id type string which is not usual with MySQL. It also doesn't create automatically the table in database when using npm migrate
Expected Behavior
It should create automatically the table user in db and be compatible with MySQL schema.
@copostic You can build your own UserService and inject in UserController the rest of example is the same
Thanks for opening the issue, let me see if I can address some of the concerns:
it's using a strict mode "false"
Relational database connectors always assumes strict: true
and ignores the user's settings. This is used for NoSQL connectors such as MongoDB to enforce a standard schema.
id type string which is not usual with MySQL
It's not the best preset for RDMBS, but it's to provide support for NoSQL connectors.
As what @dmunozb and the docs has mentioned, it's possible to extend and override the model to fit your use-case. @loopback/authentication-jwt
is meant to be a basic starting point that tries to be flexible enough to cater to as many use-cases possible.
It also doesn't create automatically the table in database when using npm migrate
Unfortunately I don't have the bandwidth to look into this at the moment, though another maintainer may be able to check on this.
Though I've caught a few erratas from the docs getting out of sync and will open a PR soon to fix that.
@jannyHou, could you please take a look when you have a chance? Thanks.
The repositories are identified by the tag 'type':'repository' (find repository bindings by tag in migrateSchema function), the UserRepository binding does not have any tags. I am not yet familiar enough with the codebase to fix it.
I have created a pull request that fixes the autocreation issue and supplies authentication-jwt repository bindings with proper repository binding tags. Incompatibility of id column with mysql still due to the varchar type remains.
If it helps anyone, I found that adding the following to migrate.ts
fixes the user and user credentials tables not being created
await Promise.all(
[
...app.find(UserServiceBindings.USER_REPOSITORY),
...app.find(UserServiceBindings.USER_CREDENTIALS_REPOSITORY)
].map(b => app.get(b.key))
);
@hernanrz Very good
Just to make the migrate code clearer It looked like this:
await app.boot();
await Promise.all(
[
...app.find(UserServiceBindings.USER_REPOSITORY),
...app.find(UserServiceBindings.USER_CREDENTIALS_REPOSITORY)
].map(b => app.get(b.key))
);
await app.migrateSchema({existingSchema});
Did any body find a solution for id string compatibility with MySQL?
Did any body find a solution for id string compatibility with MySQL?
What is your difficulty being? (code)
Because uuid (I think it would be your ID string) is working! I used What this thread handles is the error in the JWT (if you follow the tutorial on the site)
I want to use id type number because string type is not best practice with MySQL.
@tayyab-gulzar In fact, if it is uuid it must be a string (and it is not bad practice). But if you want to use id (int), the default id (put number and autoincrement) that works too!
if I use id type number then it gives error in customUserService binding Type 'import("D:/Node.js/lb4/linksupercharger-loopback-server/src/models/user.model").User' is not assignable to type 'import("D:/Node.js/lb4/linksupercharger-loopback-server/node_modules/@loopback/authentication-jwt/dist/models/user.model").User'. Types of property 'id' are incompatible. Type 'number' is not assignable to type 'string'.
if I use id type number then it gives error in customUserService binding Type 'import("D:/Node.js/lb4/linksupercharger-loopback-server/src/models/user.model").User' is not assignable to type 'import("D:/Node.js/lb4/linksupercharger-loopback-server/node_modules/@loopback/authentication-jwt/dist/models/user.model").User'. Types of property 'id' are incompatible. Type 'number' is not assignable to type 'string'.
Create your table out of JWT usage You will see that normal int or string will work
The problem is in its (use int) use in JWT, which is the theme of this issue
I don't have the code here to see But the class (to use JWT) where you inherit is a string, you can't change it to int out of nowhere
Ex:
class Super {
public id: string;
}
class Second extends Super {
public id: number;
}
See that it has nothing to do with: "id string compatibility with MySQL" It is something of the language!
I know this is old, but I was able to get this working by replacing and customizing the User Model, User Repository, User Service, and the actual JWTAuthenticationComponent.
I was also able to ditch the credentials model+repo entirely, since I store the password in the same record as the user. Additionally, I was able to switch to use a username+password input, rather than email+password.
Basically the only parts being used as-is, are the token and strategy services.
Unfortunately, none of this is out of the box with a couple lines to bind in the new model. This is because the built-in User model type is baked in to almost everything in the jwt auth component, so changing key column types, like the id, will just cause type check errors everywhere. That is why you have to replace so much stuff.
A real fix would be for JWTAuthenticationComponent to accept a type, e.g. JWTAuthenticationComponent<T, C> for the model + credentials, just like the UserService does. Though, I am not sure how that would work with the app.component binding call.
Good luck!
Hey @clewisln , I know its old. Even the current state is quite similar as before, do u still have that copy where you were able to remove the complete crendetials model+repo, I would like to have a look as I am using this jwt component but its causing many issues. One such example is I am using postgres and its not allowing existing models as they are hard coded for strict:false
setting and I have no way to change that except copying and creating my own models
@bhupeshg, I am unable to provide my code since it is an internal project at my company, but I can talk in generics. The approach I used:
A new auth service is created with a custom user model and a new credential object type. This class includes any custom authentication logic you need, and can be as simple or as complex as you like, without being required to follow the pattern in the official component.
Since this example is using different types than the binding key is expecting, typescript will throw an error if you try to bind your new service to UserServiceBindings.USER_SERVICE. So you have to copy the entire JWTAuthenticationComponent, just so you can use a different binding key. Which sucks.
Most of the component is copied as-is, except the "user bindings" section.
import {CustomUser} from '../models';
export type CustomCredentials = {
username: string;
password: string;
};
export namespace MyAuthenticationBindings {
export const MY_USER_SERVICE = BindingKey.create<UserService<CustomUser, CustomCredentials>>(
'services.user.service'
);
export const MY_USER_REPOSITORY = 'repositories.UserRepository'; //not really needed if using dep injection
}
export class MyAuthService implements UserService<CustomUser, CustomCredentials> {
constructor(
@repository(UserRepository)
protected userRepository: UserRepository,
) {}
async verifyCredentials(credentials: CustomCredentials): Promise<CustomUser> {
... custom auth logic here; return profile if success; throw error if failed
}
convertToUserProfile(user: CustomUser): UserProfile {
...
}
}
//copied from JWTAuthenticationComponent; replaced user bindings
export class MyAuthenticationComponent implements Component {
...
// user bindings
Binding.bind(MyAuthenticationBindings.MY_USER_SERVICE).toClass(MyAuthService),
Binding.bind(MyAuthenticationBindings.MY_USER_REPOSITORY).toClass(
MyUserRepository
),
...
}
export class MyApplication extends BootMixin(ServiceMixin(RepositoryMixin(RestApplication))) {
constructor(...) {
...
this.component(AuthenticationComponent);
this.component(MyAuthenticationComponent);
...
}
}
Then, just decorate your controller endpoints as normal.