Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user profile factory for authentication modules #3846

Closed
4 tasks done
jannyHou opened this issue Sep 30, 2019 · 8 comments
Closed
4 tasks done

Add user profile factory for authentication modules #3846

jannyHou opened this issue Sep 30, 2019 · 8 comments
Assignees
Milestone

Comments

@jannyHou
Copy link
Contributor

jannyHou commented Sep 30, 2019

Suggestion

This is a follow-up story for PoC #3771

Use Cases

Allow the strategy adapter to add a user profile factory that converts the returned user from a passport strategy to the user profile.

Examples

See the background and example in PoC PR

Acceptance criteria

  • Add interface UserProfileFactory and key USER_PROFILE_FACTORY in
    @loopback/authentication to help developers inject the factory wherever it's
    needed.
  • Add userProfileFactory in StrategyAdapter's constructor.
  • Tests
  • Document
@dhmlau
Copy link
Member

dhmlau commented Dec 3, 2019

@emonddr, assigning this to you per our discussion with @jannyHou.

@yiev
Copy link

yiev commented Dec 17, 2019

Hello :-)

I came here from #2246 and I am a bit confused right now - maybe someone of you can help me out of my confusion.

My goal is to have a user profile with completely different properties than UserProfile. In the mentioned issue it seemed that this may be developed in future.
But this issue seems like you are building more of a workaround instead of really enabling UserProfile to take anything the user wants it to. E.g. by turning it into an empty interface.

Did I misunderstood the goal of this user profile factory or is it just not that easy to change UserProfile to an empty interface with no properties? I didn't check the code in @loopback/security and @loopback/authentication yet, so I can't really tell.

@emonddr
Copy link
Contributor

emonddr commented Dec 17, 2019

@yiev , UserProfile is now in @loopback/security as it is used by both @loopback/authentication and @loopback/authorization .

https://github.com/strongloop/loopback-next/blob/master/packages/security/src/types.ts#L37

It extends a type called Principle which introduces
[securityId] : string
and
[attribute: string]: any;

securityId is required for authorization purposes.

[attribute: string]: any; lets you specify anything

In UserProfile itself,
we kept email and name as optional for backward compatibility.

So essentially, you can specify any fields you want.

@jannyHou
Copy link
Contributor Author

@yiev Thank you for following up the implementation in the auth modules! Right now the UserProfile interface is defined in @loopback/security, it extends interface Principal.

Let me copy the relevant definitions:

/**
 * Represent a user, an application, or a device
 */
export interface Principal {
  /**
   * Name/id
   */
  [securityId]: string;

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [attribute: string]: any;
}

/**
 * The minimum set of attributes that describe a user.
 */
export interface UserProfile extends Principal {
  // `email` and `name` are added to be identical with the
  // `UserProfile` that previously was exported by `@loopback/authentication`
  email?: string;
  name?: string;
}

It is not an empty interface but in terms of the flexibility to define custom fields, they doesn't differ a lot. The current UserProfile only has one symbol field [securityId] which is shared among all auth modules(authentication, security, authorization, and other modules in the future), and two optional fields email and name as string.

@yiev
Copy link

yiev commented Dec 18, 2019

Thank you two for your answers!
I think I got it now. 😄

So implementing my own user like this should do the job, right?

import { securityId, UserProfile } from '@loopback/security';


export class MyUser implements UserProfile {

  [securityId]: string;

  id: number;
  username: string;
  firstName: string;
  lastName: string;
  mail: string;
  otherAwesomeProperty: AwesomeClass;

  constructor(data?: Partial<MyUser>) {
    Object.assign(this, data);
  }
}

@emonddr
Copy link
Contributor

emonddr commented Dec 18, 2019

@yiev , almost.
In our view a MyUser model would be the superset of a MyUserProfile model (which implements UserProfile).

MyUser model would have many fields and would contain all data fields for the user: e.g. address, website, phone number, email, id, username, first name, last name etc.

Upon login with correct credentials (e.g. username and password or token) via authentication, the authentication framework would like to store as little information about the user on the request context in the binding SecurityBindings.USER in an object of type UserProfile.

So you should have a class named MyUserProfile which implements UserProfile...and it should not
contain all the same fields as MyUser.

This is why we are introducing a convenience function interface :

export interface UserProfileFactory<U> {
  (user: U): UserProfile;
}

which developers can use to convert a generic user type U into UserProfile .

@yiev
Copy link

yiev commented Dec 18, 2019

@emonddr That's a good advice and I do understand the point to store only the needed information in the user profile. But in my case MyUser has anyways not a lot of properties that I don't need in the user profile itself. So what you just said would happen: MyUserProfile would contain almost all the same fields as MyUser.

It makes it for me a bit easier just to have always all information in the UserProfile (even if I don't need each property on every request), than needing to ask an external datasource for the MyUser object. I think performance wise it should be also still better. The additional properties, that are not always needed, increase the size of each request by a couple of hundred bytes, but in return I don't need to ask an external datasource (in my case MySQL) for the information, because all information I need is already in UserProfile.

By the way: It's really great to have such fast response from the developers. Thank you! 😄

@emonddr emonddr closed this as completed Dec 19, 2019
@surajj
Copy link

surajj commented Oct 30, 2020

How do i bound the AuthenticationBindings.USER_PROFILE_FACTORY in my application.ts?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants