-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Document ways to properly authenticate only some methods in a controller #1334
Comments
I see two design approaches:
Considering the options, I think I like the following one best:
However, instead of reporting "CURRENT_USER was not bound" error, we should report a more helpful error explaining that the request was not authenticated. This can be achieved for example by binding Add to auth component setup: app.bind(CURRENT_USER).toDynamicValue(() => {
return Promise.reject(HttpErrors.Unauthorized());
}); On the second thought, since components can contribute only Providers, we may need to rewrite the code snippet above to use a Provider instead of a dynamic-value function. Here is the existing code that will re-bind the current user for authenticated requests - no change should be needed there: |
+1 for a more helpful error message if we throw one (should be improved anyways in case someone accidentally went down this path). I like the idea of injecting |
By injecting the While by using |
Maybe we can define a default value for |
I agree that it is preferable to have each route declare whether it requires authentication or not. 👍 Interesting questions janny. I can see how an anonymous user value might sometimes be useful, but other times we might want our route to always respond to unauthed requests with an error. (Empty responses like I don't know how the injection will look on a route, but I do hope it will have a small footprint. // Nice and short (the default strategy would be defined elsewhere)
@authenticate()
@get('/whoami')
whoAmI(): string {/*...*/}
// Acceptable
@authenticate('BasicStrategy')
@get('/whoami')
whoAmI(): string {/*...*/}
// Acceptable
@get('/whoami')
whoAmI(
@injectAuth() user: UserProfile,
): string {/*...*/}
// Oh boy. This boiler plate is a bit heavy to put on every route!
@get('/whoami')
whoAmI(
@inject(AuthenticationBindings.CURRENT_USER, { strategy: 'BasicStrategy' }) user: UserProfile,
): string {/*...*/} |
IIRC the constraints of our current authentication design, I think the following should be easy to achieve: // Use the default strategy configured elsewhere
@authenticate()
@get('/whoami')
whoAmI(
@injectCurrentUser() user: UserProfile
) {}
// Use a custom strategy
@authenticate('BasicStrategy')
@get('/whoami')
whoAmI(
@injectCurrentUser() user: UserProfile
) {} We can even shorten |
An afterthought: Would it be possible for That might reduce the boilerplate, if we don't have to keep accepting the user object (as a parameter). And it would also achieve parity with the current |
You can inject the current user in your controller constructor. Depending on how we decide to handle CURRENT_USER when no user is authenticated (throw an error or return class UserController {
constructor(
@inject.getter(AuthenticationBindings.CURRENT_USER)
public getCurrentUser: Getter<UserProfile>,
) {}
@authenticate()
@get('/whoami')
whoAmI() {
console.log('I am ', this.getCurrentUser());
}
} |
This adds the User model that implements the UserProfile interface in @loopback/authenticate. The CURRENT_USER binding is injected into the controller using @Inject(user, {optional:true}) for now to get it working for now. The verify() method also doesn't actually check the user's credentials but instead passes along an generic new User. See this thread for some good ideas: loopbackio/loopback-next#1334
Authentication is out of scope of 4.0 GA, removing this story from the release. |
@dhmlau , we can definitely have a section on this. I've only ever decorated a controller method with |
I think you should use authentication decorator/injector either on class levels, or on method levels, does not make sense trying to support both options at the same time. If there is no way to prevent this decorator/injector from being used on class or method levels, let the developer handle the problems. Like a controller might have only one method which needs authentication, let them use authentication decorator on method level. But if they use it on the class level and one of the methods does not need authentication, they should refactor their code,
Considering that this will be used in parallel with authorization decorators (or guards) on methods, what you'll end up doing is, add an authenticaton decorator at the class (controller level) so you have access to the current user and you are preventing anonymous access, and if required, add an authorization decorator on the method level, which requires a current user to check against the specified privileges on the decorator. |
Discussion with @raymondfeng @jannyHou @emonddr:
Acceptance Criteria
|
@emonddr , per the discussion in the estimation meeting, could you please review the original description and see if your docs PR is going to cover that? Thanks. |
@dhmlau , my recent docs PR #2977 only covers adding the @authenticate decorator at the method level. With regards to whether CURRENT_USER is injected in the constructor or in the method (and when {optional:true} should be specified)...I added a |
Description / Steps to reproduce / Feature proposal
Copying from #1275
There should be documentation for handling this use case in the README
See Reporting Issues for more tips on writing good issues
The text was updated successfully, but these errors were encountered: