Single page applications and rendering private pages - reactjs

I have two different account types in my application [user, admin], the two log in from the same page on the website: http://example.com/login, the log-in page sends the entered email and password to the server, the server then replies back with 200 OK HTTP code.
Now, the front-end of the Admin contains pages that a regular user should not see (think of the CMS pages), these pages must only be rendered to the admin, and shouldn't be bundled in the code and served to the regular users, even if their browsers will not render them.
because the front end of the Admin holds API endpoints that shouldn't go public (even though hitting them requires authentication).
Is it possible to make the server decide which pages (components) it should serve back to the client based on the user role in a single-page React application?
Is that available in Next JS or something like that?
I found that my question was similar to this one Is it insecure to include your login page in your single page application?, but I thought that this question deserves its own thread, because it's critically important and doesn't seem to be answered well on the internet anyway

For a Nextjs auth implementation with cookies and _middleware would look something like this:
export function middleware(req) {
const activeSession = req.headers.get('cookie');
const url = req.nextUrl.clone();
if (activeSession) {
if (req.nextUrl.pathname === '/login') {
url.pathname = '/';
return NextResponse.redirect(url);
}
return NextResponse.next();
}
url.pathname = '/login';
return NextResponse.rewrite(url);
}
You can set any cookie using any of the following methods:
https://github.com/vercel/next.js/tree/canary/examples/with-cookie-auth

Related

Django and react login with google authentication

I was trying set up google authentication with react frontend and django rest framework backend. I set up both the frontend and backend using this two part tutorial, PART1 & PART2. When I try to login with google in the frontend I get POST http://127.0.0.1:8000/google-login/ 400 (Bad Request) I think it's because my google api needs an access token and an authorization code to be passed. After debugging the react js, I noticed the response I get from google doesn't have an authorization code. I suspect because responseType is permission(by default), Source:React login props , instead of code. I was wondering how would you change the response type in react? (I'm not even sure if this alone is the issue)
Here's my backend code
In my views.py file
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = "http://localhost:3000"
client_class = OAuth2Client
in my urls.py
path('google-login/', GoogleLogin.as_view(), name='google-login'),
for my front end
/Components/login.js
const googleLogin = async (accesstoken,code) => {
console.log(accesstoken)
let res = await cacaDB.post(
`google-login/`,
{
access_token: accesstoken,
code: code
}
);
console.log(res);
return await res.status;
};
const responseGoogle = (response) => {
console.log(response.code);
googleLogin(response.accessToken, response.code);
}
return(
<div className="App">
<h1>LOGIN WITH GOOGLE</h1>
<GoogleLogin
clientId="client_id"
buttonText="LOGIN WITH GOOGLE"
onSuccess={responseGoogle}
onFailure={responseGoogle}
/>
</div>
)
I want to save the user in the database and have them stay logged in, in the front end.
This Post explains the login flow behind the scene. Here's Login flow image I'm basically stuck on returning code and accesstoken(I can return this successfully) step.
Here's my list of questions,
How do I return code from google?
I have knox token set up, can I
use it instead of the JWT tokens?
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
Would really appreciate your inputs.
After investigating a bit on my end, I think I might have a solution that works for you.
I've messed with OAuth before, and it's quite tricky sometimes because it has to be robust. So a bunch of security policies usually get in the way.
I'll provide my full step-by-step, since I was able to get it working, trying my best to match what you posted.
Firstly, to have a clean slate, I went off the example code linked in the tutorials. I cloned and built the project, and did the following:
Creating a new project on GCP
Configured the OAuth consent screen
I set the User type to "internal". This options may not be available if you're not using an account under GSuite (which I am). "External" should be fine though, just that "internal" is the easiest to test.
Created a OAuth 2.0 Client
Added http://localhost:3000 to the "Authorized JavaScript origins" and "Authorized redirect URIs" sections
Register a Django superuser
Registered a Site, with value of localhost:8000 for both fields.
Went into the admin panel, and added a Social Application with Client ID and Secret Key as the "Client ID" and "Client Secret" from GCP, respectively. I also picked the localhost site that we added earlier and added it to the right hand box. (I left Key blank)
Example of my Application Page
Filled in the clientId field in App.js, in the params of the GoogleLogin component.
Here's where I ran into a bit of trouble, but this is good news as I was able to reproduce your error! Looking at the request in the network inspector, I see that for me, no body was passed, which is clearly the direct cause of the error. But looking at App#responseGoogle(response), it clearly should pass a token of some sort, because we see the line googleLogin(response.accessToken).
So what is happening is that accounts.google.com is NOT returning a proper response, so something is happening on their end, and we get an invalid response, but we fail silently because javascript is javascript.
After examining the response that Google gave back, I found this related SO post that allowed me to fix the issue, and interestingly, the solution to it was quite simple: Clear your cache. I'll be honest, I'm not exactly sure why this works, but I suspect it has something to do with the fact that development is on your local machine (localhost/127.0.0.1 difference, perhaps?).
You can also try to access your site via incognito mode, or another browser, which also worked for me.
I have knox token set up, can I use it instead of the JWT tokens?
I don't think I have enough knowledge to properly answer this, but my preliminary research suggests no. AFAIK, you should just store the token that Google gives you, as the token itself is what you'll use to authenticate. It seems that Knox replaces Django's TokenAuthentication, which means that Knox is in charge of generating the token. If you're offloading the login work to Google, I don't see how you could leverage something like Knox. However, I could be very wrong.
Does the class GoogleLogin(SocialLoginView), take care of the steps of validating the access token and code with google and creating the user with that email in database?
I believe so. After successfully authenticating with Google (and it calls the backend endpoint correctly), it seems to create a "Social Account" model. An example of what it created for me is below. It retrieved all this information (like my name) from Google.
Example of my "Social Accounts" page
As for how to retrieve the login from the browser's local storage, I have no idea. I see no evidence of a cookie, so it must be storing it somewhere else, or you might have to set that up yourself (with React Providers, Services, or even Redux.

Can I use Firebase Storage for images accessible only via my React web app?

I'm creating a dating React web app where users can upload pictures of themselves to their user profile. I want to use Firebase storage. I want to protect the images so that they are only viewable when accessing from my web app by authenticated users - right now I get an image like this:
let storageRef = firebase.storage().ref(`images/${userid}/${userImageName}`);
// Get the download URL
storageRef.getDownloadURL().then(function(url) {
// Insert url into an <img> tag to "download"
})
This is great - but once I put the URL in the src attribute in the image tag anyone who views the source code can copy the URL and send it via email, text message, etc., making it "public". I have tried uploading images in base64 string using the putString() function also only for Firebase storage to yet again create a URL for it like a normal image upload when using the put() function.
So my question is - can I use Firebase Storage to store images and make them "private" so that only authenticated users of my web app are able to view them? Is there a way to get only the image data and use that to generate the images in the frontend/client so that no actual URLs are ever placed in the JS code?
The call to getDownloadURL() is protected by Security Rules, which means that if you write a rule like:
service firebase.storage {
match /b/{bucket}/o {
match /images/{userId}/{userImageName} {
// any authenticated user can read the bytes or get a download URL
allow read: if request.auth != null;
// only the given user can upload their photo
allow write: if request.auth.uid == userId;
}
}
}
They will not allow unauthenticated people to download URLs.
As for the second issue: once someone can see a file, assume that they have already downloaded/screenshotted it and can share it, even if the URL isn't publicly accessible (or even on the page). Viewing something is equivalent to downloading it, so there's really no difference where it's coming from as the end result can be the same.

Blank page after login using bookmarked authorization URL in IdentityServer4

We have discovered that our users very often for the first time visits our web application by browsing the direct URL of the OIDC client (https://oidcclienturl.com/), The ASP.NET Core OIDC authentication middleware kicks in and the user gets redirected back to Identityserver 4 login page.
Everything works fine but then they decide to add the (temporary? state, nonce, cookies...) authorization URL as a bookmark in their browser before entering their credentials and continuing back to the web application.
This causes an issue when the user later uses the bookmark in a new session. The login seem to actually work after entering valid user credentials even if the user uses an old authorization URL, but when the user gets redirected back to the web application they end up on a blank page (https://oidcclienturl.com/signin-oidc).
After the blank page have been loaded the user is able to browse the direct URL (https://oidcclienturl.com/) sucessfully and appear as an authentcated user in the web application.
Any ideas whats causing the blank page?
That blank page shouldnt exist, if I understand it correctly its the default callback path of the oidc authentication middleware in ASP.NET Core.
Unfortunately, the real-world problem of users bookmarking the login page isn't handled cleanly by OIDC, which requires the client app to initiate the login flow.
I've addressed this by adding a RegistrationClientId column to my user data table, which is the Identity Server ClientId corresponding to the client app that called IDS when the user account was created. In the client app configuration, we use the custom Properties dictionary to add a URI fragment:
new Client
{
ClientId = "some_client",
ClientName = "Some Client",
ClientUri = "https://localhost:5000",
Properties = new Dictionary<string, string>
{
{ "StartLoginFragment", "/Auth/StartLogin" }
}
// other config omitted
};
When a user logs in, an empty return URL indicates IDS wasn't called by a client app, so we use RegistrationClientId to query IClientStore, then we combine the ClientUri and StartLoginFragment URIs and use the resulting URI to redirect the user back to the client application.
Over in the client application, that endpoint kicks off the OIDC sign-in flow, and since the user is already signed-in on IDS, it comes right back to the correct location in the client app. The controller action looks like this:
[HttpGet]
public async Task StartLogin()
{
await acctsvc.SignOutAsync();
await HttpContext.ChallengeAsync("oidc",
new AuthenticationProperties()
{
RedirectUri = "/"
});
}
The call to SignOutAsync just ensures any client-app signin cookies are cleaned up. It's in our custom account service, but it just runs HttpContext.SignOutAsync on the usual "Cookies" and "oidc" schemes. Normally that would also result in a signout call to IDS, but the redirection by the subsequent ChallengeAsync replaces the pending signout call.
The downside is that the action is an HTTP GET meaning pretty much anyone could theoretically trigger this action. At most it would be an annoyance.
In the special case where your IDS is only handling auth for a single client, you can skip a lot of that -- if they land on the page with no return URL, just send them to your client app start-login endpoint straightaway, before they login.

Preventing Direct URL to views in angular + firebase web app

I'm not an expert as I started working in firebase and angularjs a few weeks ago. I'm building a server based web application using firebase, nodejs(for server) and angularjs and I've a login scenario for my web application, Now I want to prevent direct URL access, after checking that a user is login or not. I want to redirect it to login page if user try to access via direct URL.
I tried using this but it doesn't seems to work.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
Can some provide me a working example of preventing access to URLs using angularjs and firebase?
Thanks in Advances

Multiple Login Screens and Namespaces

I have a problem I can't figure out, in my application I have two login screens, one for the admin the other for the regular user. These login screens however point to the same controller in the same Auth namespace aside from that I have separated the rest of my controllers and routes into two namespaces; FrontEnd Namespace and BackEnd namespace.
Now my issue right now is when a regular user uses their credentials to login from the backend they are allowed access, I installed this package kodein/acl to handle roles and permissions and it seems to be working because when a user logs in to the backend now they can't do anything. The problem now is that even though they can't do anything they are still able to redirect to admin dashboard.
What I want here is this; when a regular user tries to login to admin backend they are denied access. I am a bit confused, i dunno how to o about it.
Do I have to create separate auth controllers and methods in both namespaces? Is that even possible? How would i go about it?
I use AngularJS for my frontend so in my route file i have this:
Route::group(['domain' => 'admin.website.loc'], function() {
Route::any('{url?}', function($url) {
return view('backend.index');
})->where(['url' => '[-a-zA-Z0-9/]+']);
});
Route::any('{url?}', function($url) {
return view('frontend.index');
})->where(['url' => '[-a-zA-Z0-9/]+']);
Which catch all urls and return to a single Index page,on the front end since i use JWT for authentication it validates the token on the frontend and if invalid or not available takes the user to the login page.
I thought of something else, maybe a temporary measure maybe permanent, i added the following code to my login function():
$url_parts = parse_url($request->url());
$host_parts = explode('.', $url_parts['host']);
if ($host_parts[0] == "admin")
{
$user = User::find(Auth::user()->id);
if (!$user->is('administrator'))
{
Auth::logout();
return response()->json(['error' => 'You Are Not Authorized!']);
}
}
first i get the request url,
then i get the "host" part of the request url and split it using '.' as the delimiter, this way i can check the subdomain.
Since my admin side uses a sub domain i check to see if the login request was from a url with the 'admin' subdomain, if it was then i check the authenticated user's role to see if they're administrator, if they are not, i log them out immediately and return an error message.
This way i don't have to create a separate function and route for the two login screens.

Resources