I've just started using loopback4 and I would like to protect the /explorer from being public. The user would initially see a page where username and password must be entered. If successful, the user is redirected to /explorer where he can see all API methods (and execute them). If user is not authenticated, accessing the path /explorer would give a response of "Unauthorized". Is there a way to easily implement this?
There is issue talking about a GLOBAL default strategy is enabled for all routes including explorer in https://github.com/strongloop/loopback-next/issues/5758
The way is to specify a global metadata through the options:
this.configure(AuthenticationBindings.COMPONENT).to({
defaultMetadata: {
strategy: 'JWTStrategy'
}
})
this.component(AuthenticationComponent);
registerAuthenticationStrategy(this, JWTAuthenticationStrategy)
But in terms of enabling a single endpoint added by route.get(), it's not supported yet, see code of how explorer is registered. #loopback/authentication retrieves auth strategy name from a controller class or its members, but if the route is not defined in the controller, it can only fall back to the default options, see implementation
Related
OpenUI5 version: 1.86
Browser/version (+device/version): Chrome Dev
Upon the authentication I validate the user session:
if (isUserSessionValid) {
const oRouter = UIComponent.getRouterFor(this);
oRouter.navTo("overview");
} else {
this.getOwnerComponent().openAuthDialog();
}
If isUserSessionValid is true, then I forward an user to the internal page, otherwise I show the login dialog.
The problem is, however, that an user can change the value of isUserSessionValid in DevTools and then getting forwarded to the UI5 app internal page. Of course, due to a lack of a valid session, no piece of the business data will be displayed, just an empty UI5 app template, but I would like to prevent even such screen.
If it would be a classical webapp, I would just send an appropriate server response with a redirect to the login page (e.g. res.redirect(403, "/login");). But, if I understand it correctly, since I'm sending am asynchronous request, a plain res.redirect won't work out and I'm required to implement a redirection logic on the UI5-client, which can be manipulated and bypassed by user.
How to prevent a manipulation of a view navigation in UI5 and ensure that unauthorized user can't get any piece of the UI5-app code?
The answer from SAP:
If you want to prevent an unauthorized user from accessing the client-side code (e.g. view/controller) you need to enforce
authorization on the server also for those static files. When bundling
the application code you also need to ensure that those files are
separate from the "public" files. One approach would be to have 2
separate components, one for the public page/auth dialog and one for
the actual application.
recently in one of my applications I needed to access currently logged-in user data for saving in another model (something like the author of a book or owner of a book). in my googling, I encountered these references but none of them was useful.
https://github.com/strongloop/loopback/issues/1495
https://docs.strongloop.com/display/public/LB/Using+current+context
...
all of them have this problem about accessing context or req object. after three days I decided to switch to afterRemote remote hook and add Owner or Author on that stage.
but something was wrong with this solution.
in strongloop's documentations (https://docs.strongloop.com/display/public/LB/Remote+hooks) there is a variable as ctx.req.accessToken that saves current logged-in user access token. but in the application this variable is undefined.
instead, I found accessToken in ctx.req.query.access_token and it was currently access_token variable that is sent to the server.
here is my problem:
is this variable (ctx.req.query.access_token) always available or
it's just because loopback-explorer send access_token as GET
variable?
in production mode do applications need to send access_token as
GET variable or it should be sent as Authorization in the header?
why ctx.req.accessToken is undefined?
could these things change over time? cause most of users encounter this problem due to deprecation of app.getCurrentContext()
Is this variable (ctx.req.query.access_token) always available or
it's just because loopback-explorer send access_token as GET
variable?
Well if your application always sends in the querystring, then it'll be always available for you, but it also sent in the header, or cookie or in the request body, but I don't suggest using it because it if the user logged in and the access token is valid and ctx.req.accessToken should be available and you can use it.
In production mode do applications need to send access_token as
GET variable or it should be sent as Authorization in the header?
I believe Authorization header is preferred, as if you send it in a GET variable, well it'll be visible in the logs and someone with the access to the logs can access the session(well unless you trust everyone), other than this it's fine to have it in a GET variable. Though I believe loopback client SDKs(Angular, Android, iOS) all send it via Authorization header by default, so you might have to configure them(maybe not possible).
Why ctx.req.accessToken is undefined?
Sometimes the context is lost thanks to the database drivers connection pooling, or the context req is lost(ctx.req) and they are null.
Assuming ctx.req is defined(because sometimes it's not), then probably that means the user is not logged it, or it's access token wasn't valid(expired or not in database). Also it could be a bug(maybe misconfiguration on your side), which also means for you that you will authentication problems.
Could these things change over time? cause most of users encounter this problem due to deprecation of app.getCurrentContext()
app.getCurrentContext is risky to use and I don't suggest unless you have no other solution. If you use it and it works, it might stop working if the database driver changes or in some corner cases that you haven't tested it, it might not work.
In the updated doc https://loopback.io/doc/en/lb3/Using-current-context.html
add this in your remoting metadata
"accepts": [
{"arg": "options", "type": "object", "http": "optionsFromRequest"}
]
then
MyModel.methodName = function(options) {
const token = options && options.accessToken;
const userId = token.userId
}
but it says
In LoopBack 2.x, this feature is disabled by default for compatibility reasons. To enable, add "injectOptionsFromRemoteContext": true to your model JSON file.
so add "injectOptionsFromRemoteContext": true on your model.json file
I use the security.yml with access_control to secure the API paths based on the user role. This works fine, but how do I secure specific parameters like /api/project/:id?
Different users have access to different project ids. Therefore a database call has to be made to check if this user has access to this project.
I tried to use $this->denyAccessUnlessGranted('GET', $projectId, 'Unauthorized access!'); in the ProjectController, which calls a custom Voter to check the database and therefore the access.
public function getProjectAction(Request $request, $id)
{
$this->denyAccessUnlessGranted('GET', $id, 'Unauthorized access!');
This works, but it seems very unpractical to add this code to 10+ actions in the ProjectController alone and also in many parts of the API.
Therefore my question: What is the best pratice to secure a REST api with symfony2, fosUserBundle and fosRestBundle
I would suggest introducing security voters.
http://symfony.com/doc/current/cookbook/security/voters_data_permission.html
Also create some kind of exception handler / listener, to catch your exceptions and make a specific error response.
http://symfony.com/doc/current/cookbook/service_container/event_listener.html
I have ServiceStack v4 service but when I call the auth/logout route (using either POST or GET) to logout the currently logged-in user, I get an error:
400 Not Empty
User Name cannot be empty
Password Cannot be empty
As I wouldn't expect users to enter credentials when logging out, I am surely missing something?
I have the AuthFeature registered during host initialisation, and I am using CredentialsAuthProvider. I have taken the code from Github so I can see how it works.
My Client Code:
var rest = Restangular.one('auth/logout').get();
//var result = rest.post({userName: userName});
this.requestTracker.addPromise(rest);
return rest;
After a lot of digging, this happens when you are using CredentialsAuthProvider. Within this class, a validator is defined that validates all instances of the Authenticate request. As the logout route uses the Authenticate request, this validator is fired.
I got round it by modifying the validator to:
RuleFor(x => x.UserName).NotEmpty().When(d => d.provider != "logout");
RuleFor(x => x.Password).NotEmpty().When(d => d.provider != "logout");
This is probably not the most elegant way of fixing long term, but got me up and running.
I know this question is old, but I recently have been struggling with the same thing. What occurs is that before the Authenticate.Post function is called, the validation cache is checked and the CredentialsAuthProvider which has the mentioned validator fails unless username and password are not empty.
Now, i'm not sure if it makes a difference if you only have that provider enabled or not - I've not tested. I actually have my own custom provider that subclasses CredentialsAuthProvider and it's the only one I register.
The only way currently is to either pass a non-empty (but useless) password and username, or modify your own custom provider, overriding the Authenticate function and using a modified version of the validator as mentioned above.
I am using the Spring Security Core plugin and can successfully log users in and out of my application. However, when the user successfully signs up, I don't understand how to set that user's security context on the signup postback so that they can be redirected to a secure page without having to log in again. Any help is welcome. Thanks.
The other link you reference is 2 years old. Since then I've added a reauthenticate method to SpringSecurityService. See section "6.2 SpringSecurityService" in the docs:
http://grails-plugins.github.com/grails-spring-security-core/docs/
I eventually came upon this link, which does the trick: https://stackoverflow.com/a/7112838/469106
Here are the contents of that link:
If you don't have the password, you can load the user via
def user = User.findByUsername(username)
and setting the authority array in the 3-parameter constructor. Create the auths via
GrantedAuthority[] auths = user.authorities.collect { new GrantedAuthorityImpl(it.authority) }
Then you can omit the call to authenticate() and use:
SecurityContextHolder.context.authentication = new UsernamePasswordAuthenticationToken(username, 'unknown', auths)