I have a basic PHP app, where the user login is stored in the HTTP Session. The app has one main template, say index.html, that switch sub-view using ngView, like this
<body ng-controller='MainCtrl'>
<div ng-view></div>
</body>
Now, this main template can be protected via basic PHP controls, but i have sub-templates (i.e. user list, add user, edit user, etc.) that are plain html files, included from angular according to my route settings.
While i am able to check for auth what concern the request of http services, one user is able to navigate to the sub-template url and access it. How can i prevent this from happen?
I would create a service like this:
app.factory('routeAuths', [ function() {
// any path that starts with /template1 will be restricted
var routeAuths = [{
path : '/template1.*',
access : 'restricted'
}];
return {
get : function(path) {
//you can expand the matching algorithm for wildcards etc.
var routeAuth;
for ( var i = 0; i < routeAuths.length; i += 1) {
routeAuth = routeAuths[i];
var routeAuthRegex = new RegExp(routeAuth.path);
if (routeAuthRegex.test(path)) {
if (routeAuth.access === 'restricted') {
return {
access : 'restricted',
path : path
};
}
}
}
// you can also make the default 'restricted' and check only for 'allowed'
return {
access : 'allowed',
path : path
};
}
};
} ]);
And in the main/root controller listen for $locationChangeStart events:
app.controller('AppController', ['$scope', '$route', '$routeParams', '$location', 'routeAuths',
function(scope, route, routeParams, location, routeAuths) {
scope.route = route;
scope.routeParams = routeParams;
scope.location = location;
scope.routeAuth = {
};
scope.$on('$locationChangeStart', function(event, newVal, oldVal) {
var routeAuth = routeAuths.get(location.path());
if (routeAuth.access === 'restricted') {
if (scope.routeAuth.allowed) {
event.preventDefault();
}
else {
//if the browser navigates with a direct url that is restricted
//redirect to a default
location.url('/main');
}
scope.routeAuth.restricted = routeAuth;
}
else {
scope.routeAuth.allowed = routeAuth;
scope.routeAuth.restricted = undefined;
}
});
}]);
Demo:
plunker
References:
angularjs services
location
UPDATE:
In order to fully prevent html template access then it's best done on the server as well. Since if you serve the html from a static folder on server a user can access the file directly ex: root_url/templates/template1.html thus circumventing the angular checker.
If you want to block them from going to that page create a service: http://docs.angularjs.org/guide/dev_guide.services.creating_services
This service can be dependency injected by all your controllers that you registered with the routeParams.
In the service you can would have a function that would check to see if the person is logged in or not and then re-route them (back to the login page perhaps?) using http://docs.angularjs.org/api/ng.$location#path. Call this function in each of the controllers like so:
function myController(myServiceChecker){
myServiceChecker.makeSureLoggedIn();
}
The makeSureLoggedIn function would check what current url they're at (using the $location.path) and if it's not one they're allowed to, redirect them back to a page that they are allowed to be.
I'd be interested to know if there's a way to prevent the routeParams from even firing, but at least this will let you do what you want.
Edit: Also see my answer here, you can prevent them from even going to the page:
AngularJS - Detecting, stalling, and cancelling route changes
Related
I am using Next.js and I have a folder learning inside my pages folder. Now, this learning folder has about 10 pages.
All these pages need to redirect to the index page if the user is not logged in. The following code does the job, but is there any other way to protect multiple pages, so that I don't need to add this same code again and again to all the pages ?
export async function getServerSideProps(context) {
//redirect to index page if not logged in
const session = await unstable_getServerSession(context.req, context.res, authOptions);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false
}
}
}
}
I believe you are confused between protecting pages and protecting API ROUTES.
If you simply want to protect pages, you can indeed use middleware
However, if you wish to protect API Routes (e.g prevent a user from deleting data using your API endpoint and postman), I believe you need to use this unstable_getServerSession
Except creating reusable function, it's true that I didn't find anywhere in the doc how to set it for multiple paths in one folder only...
you can use middleware. docs: https://next-auth.js.org/configuration/nextjs#middleware
Create a middleware.ts (or .js) file at the root or in the src directory (same level as your pages).
If you only want to secure certain pages, export a config object with a matcher:
export { default } from "next-auth/middleware"
// otherwise your app would require authentication for all
export const config = { matcher: ["/dashboard"] }
Now you will still be able to visit every page, but only /dashboard
will require authentication.
If a user is not logged in, the default behavior is to redirect them
to the sign-in page.
that example is from the docs. You can also write a custom middleware
import { NextResponse } from "next/server";
export function middleware(req) {
const sessionCookie = req.cookies.get("session");
}
// you could add more if logic for other pages
if (req.nextUrl.pathname.startsWith("/admin ")) {
if (!sessionCookie) {
return NextResponse.redirect("/home");
}
}
navbar.template.html
<div id="g_id_onload"
data-client_id="832#################m921.apps.googleusercontent.com"
data-cancel_on_tap_outside="false"
data-login_uri="http://localhost:3010/auth/g-one-tap"
data-callback="handleCredentialResponse">
</div>
The API get the response I am able to validate the user and return the validated JWT token, how can I capture the response and avoid the redirection of the page to http://localhost:3010/auth/g-one-tap
How can I us some click function to be used in typsecript file which can help in following the normal login flow which I am using earlier with google login button.
public socialSignIn(responseData) {
this.googleSubscription = this._globalService.googleLogin(responseData)
.subscribe(
data => {
if (data['success']) {
const token = data['data']['token'];
if (this.platformId === 'browser') {
// login to save the token
}
}
},
error => {
console.log('error');
}
);
}
As mentioned here, you should not use both data-login_uri and data-callbck attributes at the same time.
You need to remove the data-login_uri attribute in you code.
And povide an implementation for the callback function (whose name is handleCredentialResponse in your code) if not yet.
I am having trouble getting dependency injection working for my AuthorizerService. Obviously, dep-inj is not ready until after Aurelia "starts", but I wasn't sure how to access it.
main.js:
aurelia.container.registerInstance(HttpClient, http.c());
// set your interceptors to take cookie data and put into header
return aurelia.start().then(() => {
let Authorizer = new AuthorizerService();
aurelia.container.registerInstance(AuthorizerService, Authorization);
console.log('Current State: %o', Authorizer.auth);
Authorizer.checkCookieAndPingServer().then(() => { console.log('Current State: %o', Authorizer.auth); aurelia.setRoot(PLATFORM.moduleName('app')); }, () => { aurelia.setRoot(PLATFORM.moduleName('login-redirect')); });
});
Now the problem is that if I do "new AuthorizerService()" then "this.http.fetch()" is not available in AuthorizerService.js.
Am I meant to pass "http.c()" (which delivers the HttpClient instance) as a parameter inside:
checkCookieAndPingServer(http.c())
or is there another way?
Can I delete "new AuthorizerService()" and just do (I made this up):
aurelia.container.getInstance(AuthorizerService);
Somehow FORCE it to do dependency-injection and retrieve the "registered Instance" of "http.c()"?
I can't just check cookie. I have to ping server for security and the server will set the cookie.
I think this is all sorts of wrong, because I need a global parameter that just is false by default, then it does the query to backend server and setsRoot accordingly. Perhaps only query backend in the "login page"? Okay but then I would need to do "setRoot(backtoApp); aurelia.AlsoSetLoggedIn(true);" inside login module. But when I setRoot(backtoApp) then it just starts all over again.
In other words, when setRoot(login); then setRoot(backToApp); <-- then AuthorizerService instance doesn't have its proper data set (such as loggedIn=true).
EDIT: Better Solution maybe:
main.js:
return aurelia.start().then(() => {
let Authorizer = aurelia.container.get(AuthorizerService);
let root = Authorizer.isAuthenticated() ? PLATFORM.moduleName('app') : PLATFORM.moduleName('login');
console.log('Current State: %o', Authorizer.auth);
aurelia.setRoot(root);
});
Authorizer.js
constructor(http) {
this.http = http;
this.auth = {
isAuthenticated: false,
user: {}
}
}
"this.auth" is no longer static. No longer "static auth = { isAuthenticated: false }" which was some example code I had found.
So now "auth" gets set inside "login" module. But this means the "login" module is displayed every single time the app loads briefly, before being redirected back to "setRoot(backToApp)"
If the class you want to get the instance is purely based on service classes and has no dependencies on some Aurelia plugins, it doesn't need to wait until Aurelia has started to safely invoke the container.
For your example:
aurelia.container.getInstance(AuthorizerService);
It can be
aurelia.container.get(AuthorizerService);
And you should not use new AuthorizerService(), as you have noticed in your question.
I am starting with Polymer and Firebase and have implemented the Google OAuth authentication.
I have notice the page loads before authentication and if you click back you can get to the page without authorization, albeit that you are not able to use the firebase api and therefore the page is not usable.
My issue is that I do not want my javascript loaded until authenticated.
How could this be done.
Many thanks
It depends if your using firebase or their polymer wrapper, polymerfire.
Create a document for all the imports that you want to be conditionally loaded
// user-scripts-lazy.html
<link rel="import" href="user-script-one.html">
<script src="script.js"></script>
// etc
Using Polymerfire
In the element that hosts <firebase-auth> create a observer and you'll expose some variables from firebase-auth.
<firebase-auth
user="{{user}}"
status-known="{{statusKnown}}"></firebase-auth>
In the observer, watch the user element and the status known
statusKnown: When true, login status can be determined by checking user property
user: The currently-authenticated user with user-related metadata. See the firebase.User documentation for the spec.
observers:[
'_userStateKnown(user, statusKnown)'
]
_userStateKnown: function(user, status) {
if(status && user) {
// The status is known and the user has logged in
// so load the files here - using the lazy load method
var resolvedPageUrl = this.resolveUrl('user-scripts-lazy.html.html');
this.importHref(resolvedPageUrl, null, this.onError, true);
}
}
To get the state without using polymerfire you can use onAuthStateChange
properties: {
user: {
type: Object,
value: null // important to initialise to null
}
}
..
ready: function() {
firebase.auth().onAuthStateChagned(function(user) {
if(user)
this.set('user', user); // when a user is logged in set their firebase user variable to ser
else
this.set('user', false); // when no user is logged in set user to false
}.bind(this)); // bind the Polymer scope to the onAuthStateChanged function
}
// set an observer in the element
observers: [
'_userChanged(user)'
],
_userChanged: function(user) {
if(user === null) {
// authStatus is false, the authStateChagned function hasn't returned yet. Do nothing
return;
}
if(user) {
// user has been signed in
// lazy load the same as before
} else {
// no user is signed in
}
}
I haven't tested the code while writing it here, but i've implemented the same thing various times.
There are a couple of options.
Put content you don't want loaded behind a dom-if template with "[[user]]" as its driver. This could include your firebase element, so the database isn't even considered until after log on.
Put a modal dialog box up if the user is not logged on. I do this with a custom session element . Whilst the overlay is showing then the rest of the page is unresponsive to anything.
If it is simply an aesthetic issue of removing the non-logged-in page from view, could you either hide the page (or display some kind of overlay) while the user isn't authenticated?
I currently have this in an current project for some elements: hidden$="{{!user}}"
I have identified the solution for my purpose ...
Add storage role based authorization (see is there a way to authenticate user role in firebase storage rules?)
This does have a limitation currently of hard coded uid's
In the page, request storage resource and if successful include it in the dom (i.e. add script element with src pointing to storage url)
Call javascript as normal
Not sure if this is possible, but here it goes.
What I am looking to do is include my "admin" routes as a separate file, only if the user is an admin (therefore a non admin will get a 404 error
routes.php
if( Session::get('user')->is_admin )
require_once('routes-admin.php');
if( Auth::check() )
require_once('routes-user.php');
Route::get('/', function() {
return view('home');
});
routes-admin.php
Route::get('admin', function() {
return view('admin-dashboard');
});
routes-user.php
Route::get('user', function() {
return view('user-dashboard');
});
What I am trying to do is avoid having the test repeated with every single Route
so if my user segment has 10 pages I currently need 30 lines of code dedicated to Auth::check() (the if, else and redirect if not), where I can instead have a single check on routes.php and the user will get a 404 if they don't belong
Is there a way to perform this check outside of the Route?
Perhaps you want to read documentation first?
Route::group(['middleware' => 'auth'], function()
{
Route::get('/', function()
{
// Uses Auth Middleware
});
Route::get('user/profile', function()
{
// Uses Auth Middleware
});
});
Above code does exactly what you need, is "person logged in?" let him go to page "whatever".
You can create middlewares (check if user is admin or basic user) yourself and apply on groups.
Example middleware
class BeforeMiddleware implements Middleware
{
public function handle($request, Closure $next)
{
// Perform action
return $next($request);
}
}
Do not get me wrong, just your approach is really not Laravel like. Try to see some open source projects done in L5 or even in L4. Try to use everything Taylor already done for you. Documentation is your firend here.
Following the response of #Kyslik for the middleware, you can "include" your own routes file in your RouteServiceProvider like the default routes file, the RouteServiceProvide is located in: app/Providers/RouteServiceProvider.php,
Find the section
require app_path('Http/routes.php');
and just replicate with the name of your routes file want to include