I am building AngularJs and WebApi application, usin Aspnet Core rc1. I have problem with returning static index.html file. I have tried several methods. The first method was to use such code in Startup.cs
app.UseFileServer();
app.UseMvc();
In this way it works, if I call http://localhost:29838 (root url). But if I go on http://localhost:29838/books ( /books root is my angular root defined using ng-route, and I am using html5 mode) and renew the page, server will return 404 mistake of course.
Then, I read this article https://dzone.com/articles/fixing-html5mode-and-angular .I have tried to use rewrite module method in web.config/ Everything works fine. But I do not like this method. As I have understood it works only with IIS.
Finally, I have tried to use the first way, described in article (when Home/Index returns html file).controller code is:
public ActionResult Index()
{
return File("~/index.html", "text/html");
}
and Startup.cs is:
routes.MapRoute(
name: "Default",
url: "{*.}",
defaults: new
{
controller = "Home",
action = "Index",
}
);
Using this approach I have such erros: Refused to load the script 'http://localhost:29838/libs/jquery/dist/jquery.min.js' because it violates the following Content Security Policy directive: "script-src 'unsafe-inline'". And I can not overcome this. What am I doing wrong?
Try to use AspnetCore.SpaServices, they provide you legit HTML5 spa-fallback.
Something like this:
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
This library fix all your issues with SPA-fallback.
The SpaServices mentioned by #Andrei is great if you are using an MVC page that has server side render or some server logic.
If you don't have any logic in the server side, and you don't need it, you could just do the following:
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/index.html";
await next();
}
})
.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "index.html" } })
.UseStaticFiles()
What the snippet does is look for pages that don't exist on the server side, and redirect them to '/index.html'. If you have a different entry point file, you may change the Url. Also, if the file requested has extension, it is served in order to allow javascript, css, html etc to be rendered on the client.
You can also take a look here: https://code.msdn.microsoft.com/How-to-fix-the-routing-225ac90f
Finally. I have found the solution. For security I use OpenIdConnect.Server. In sample project author used NWebsec.Middleware. And I have just copied csp settings from this sample project
app.UseCsp(options => options.DefaultSources(configuration => configuration.Self())
.ImageSources(configuration => configuration.Self().CustomSources("data:"))
.ScriptSources(configuration => configuration.UnsafeInline())
.StyleSources(configuration => configuration.Self().UnsafeInline()));
Notice, that authore misspelled calling .Self() method after ScripSources(). And I have not noticed that !!! Be carefull with copy and paste
Related
If I manually go to this url: https://localhost:1234/signout-oidc
Then it signs out of my azure AD connected mvc application.
However going to this url just presents me with a blank white screen. I'm trying to do this on a 'log out' button on my MVC site, so ideally I would have it redirect. I might also want to do some custom logic so it would be good if I could put this into an action.
I've seen some suggestions like this:
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
However nothing happens when running these lines, I remain signed in.
Can anyone tell me what the correct way is to sign out the way that URL does within my application?
In the controller, simply put:
return SignOut("Cookies", "OpenIdConnect");
To control the URL, in the program.cs or startup.cs, put a handler in:
builder.Services.AddAuthentication(authOptions =>
{
authOptions.DefaultScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events.OnSignedOutCallbackRedirect += context =>
{
context.Response.Redirect("/"); // Redirect URL
context.HandleResponse();
return System.Threading.Tasks.Task.CompletedTask;
};
});
I am having this problem that whenever i try to visit the page localhost:3000/blog/test directly it returns a 404 error. But whenever i try to visit it using <Link> component it works fine.
This is my code <Link href={{ pathname: '/blog', query: {slug: 'test'} }} as="/blog/test"><a className="nav__link">Blog</a></Link>
and i have a file blog.js in my pages folder.
What's happening is that on the client, with the Link component, you are creating a link to the blog.js page by setting "/blog" as the pathname.
When you go directly to the URL/blog/test, Next.js will try to render the page on the server and to do so will look for the file /pages/blog/test.js. That file doesn't exist, and Next.js doesn't know that you want to load the blog.js page and set query.slug to to the second part of the URL.
To do this, you need to map that route on the server to load the page you want, and pull the params you want out of the URL.
The Next.js docs cover this in Server Side Support for Clean URLs by using express to setup a custom server.
You can view the full code to get it working there, but your custom route will look something like this:
server.get('/blog/:slug', (req, res) => {
const actualPage = '/blog'
const queryParams = { slug: req.params.slug }
app.render(req, res, actualPage, queryParams)
})
You'll have to use now.json to set up your routes. Also it is important to note that it's now that builds the route so visiting it on the client side wont work if you are using localhost. Build your project with now and it should work.
Also the "as" parameter would be as={{ pathname:/user/manage/${variable}}}
I am struggling to get angular-pdfjs-viewer working in my angular app that's written in Typescript.
I am always getting a red error message with the text:
PDF.js v1.7.354 (build: 0f7548ba)
Message: stream must have data
The PDF data is coming from an ASP.NET WebAPI controller, and the architecture of our application has the front facing website and the API both deployed as two independent web applications.
Due to this, I am wanting to download the PDF in the angular controller, and set the PDF source to the data attribute of the directive.
The WebAPI returns the PDF like this:
[HttpGet]
[Route("invoice/{id}/originalpdf")]
public async Task<ActionResult> GetInvoiceSourceImagePdf(string id)
{
var userId = User.Identity.GetMyId();
var bytes = await _serviceThatReturnsByteArray(id);
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
}
I don't see an issue with the above. In the browser, if I hit this endpoint, I get the document rendering exactly as expected.
On the front-end side:
We're using the angular-pdfjs-viewer from https://github.com/legalthings/angular-pdfjs-viewer
We're including (via BundleConfig), sources in the order of: pdf.js, then angular.js and then finally angular-pdfjs-viewer.js (with other application required dependencies in between)
I am using the PDF.js dependency that "ships" with angular-pdfjs-viewer.js
The angular controller looks like this:
module MyApp {
class PdfDialogController {
public static $inject = ["$scope", "$window", "$log", "$uibModalInstance", "$http"];
pdfData: Uint8Array;
constructor(/*stuff*/){
this.$http.get(the_url, { responseType: 'arrayBuffer' })
.then((t) => {
this.pdfData = new Uint8Array(<ArrayBuffer>t.data);
});
}
}
}
And in the view, we have:
<div class="modal-body">
<div id="pdf-container">
<pdfjs-viewer data="vm.pdfData"></pdfjs-viewer>
</div>
</div>
Inspecting the network in the browser, the PDF content is downloaded.
t.data in the controller contains data that starts with
%PDF-1.3\n%����\n1 0 obj\n[/PDF /Text]
However, the views shows this:
And the console outputs an error message:
Error: An error occurred while loading the PDF.
What am I doing wrong, and how can I determine what the issue is with the PDF data (if there is one).
Well, it turns out that I was using
{ responseType: 'arrayBuffer' } // camel case
when I should be using
{ responseType: 'arraybuffer' } // all lowercase
Once changed, the code I posted in the question worked perfectly.
I'm using Laravel and Angular to write a web app.
In the front end Laravel is used to create the basic template, but otherwise controlled by Angular. In the back end laravel is used to create a restful API.
I have a few routes like this:
Route::group(['domain' => 'domain.com'], function() {
Route::get('/', ['as' => 'home', function () {
return view('homepage');
}]);
Route::get('/login', ['as' => 'login', function () {
return view('login');
}]);
//users should be authenticated before accessing this page
Route::get('/dashboard', ['as' => 'dashboard', function () {
return view('dashboard');
}]);
});
Route::group(['domain' => 'api.domain.com', 'middleware' => ['oauth']], function() {
Route::post('/post/create', ['uses' => 'PostController#store']);
Route::get('/post/{id}', ['uses' => 'PostController#show']);
//other API endpoints
// ...
});
I want to make sure my domain.com/dashboard URL is only accessed by authenticated users.
In my backend I have OAuth implemented for my API routes which makes sure the user accessing those routes are authentic. Laravel's Auth::once() is used by the OAuth library to make sure the user credentials are correct then generates an access_token. Since Auth::once() is a "stateless" function no session or cookies are utilized and I cannot use Auth::check() to make sure a user is authenticated before the dashboard page is rendered.
How should I go about checking to see if the user trying to access domain.com/dashboard is authenticated? Should I send the access_token in the header when I forward the user from /login to /dashboard? Or should I implement Laravel's a session/cookie based authentication?
EDIT: As per this: Adding http headers to window.location.href in Angular app I cannot forward the user to the dashboard page with an Authorization header.
In order to reuse my API for my mobile apps I STRONGLY prefer to use some sort of token based authentication.
I would advise to use JWT (JSON Web Tokens) to control for authentication.
I think there are several tutorials for their use with Lavarel and AngularJS. I'm more into Python and I use Flask, but the followings look interesting :
Simple AngularJS Authentication with JWT : the AngularJS configuration
Token-Based Authentication for AngularJS and Laravel Apps : the connection with Laravel
JSON Web Token Tutorial: An Example in Laravel and AngularJS
Pierre was right in suggesting JWT for your token based auth.
When the user successfully logs in, before you finish the request, you can create a JWT and pass that back to the client. You can store it on the client (localStorage, sessionStorage) if you want. Then on subsequent requests, put the JWT inside of your Authorization header. You can then check for this header in your middleware and prevent access to your API routes if the token is valid. You can also use that token on the client and prevent Angular from switching routes if the token doesn't exists or isn't valid.
Now if you are trying to prevent the user from accessing the page entirely on initial load (Opens browser, goes straight to domain.com/dashboard), then I believe that is impossible since there is no way to get information about the client without first loading some code on the page.
Not sure about Angular, as I have never used it, but have you tried targeting a controller with your dashboard route? For example
Route::get('/dashboard', [
'uses' => 'UserController#getDashboard',
'as' => 'dashboard',
'middleware' => 'auth'
]);
UserController.php (I'm assuming you have a blade called dashboard.blade.php)
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class UserController extends Controller
{
public function getDashboard()
{
if(Auth::user()) {
return view('dashboard');
} else {
redirect()->back();
}
}
}
Also, you could always group whichever routes you want to protect with this (taken from the Laravel 5.2 documentation):
Route::group(['middleware' => 'auth'], function () {
Route::get('/', function () {
// Uses Auth Middleware
});
Route::get('user/profile', function () {
// Uses Auth Middleware
});
});
EDIT: Regarding the session token, your login blade should have this in its code:
<input type="hidden" name="_token" value="{{ Session::token() }}">
When the /dashboard HTML loads, does it already include data specific to the user's account?
I would suggest to load the /dashboard HTML separately from the user's data, and hide the dashboard items with ng-cloak while Angular loads the data to populate it.
Redirect to /dashboard URL
Load (static?) dashboard HTML / Angular app, hiding all parts with ng-cloak.
Have Angular access the API using the access_token to load all dashboard data.
Revealing the parts of the dashboard when the data from the API comes in, or showing an error message if the access_token wan't valid.
That way, your /dashboard HTML could actually be just a static HTML file and directly served by the web server and cached by any proxy on the way.
If that isn't an option, you could put your access_token into a cookie with Javascript that runs on the /login view, then redirect to /dashboard, then have your server-side /dashboard view read the cookie to check if the access_token is valid. But that sounds messy and mixes up things that should be separated.
#Pierre Cordier #Mr_Antivius Thank you guys for your answer, it helped me get insight into the problem and allowed me to tinker with with JWT but ultimately did not product a solution for me.
To allow only authenticated users to access domain.com/dashboard I had to implement a hybrid session and OAuth authentication system. I decided to go with Sentinel (instead of Laravel's out of the box auth system) because it has a user permission system I need in other places in my app. I use this library for the OAuth Server.
Here is what I do in my controller:
POST domain.com/auth/authenticate:
public function processLogin(Request $request)
{
$credentials = [
'email' => $request->input('username'),
'password' => $request->input('password'),
];
try
{
$sentinel = Sentinel::authenticate($credentials);
}
catch (\Cartalyst\Sentinel\Checkpoints\ThrottlingException $e)
{
$response = ['error' => [$e->getMessage()]];
$httpStatus = 429;
return response()->json($response, $httpStatus);
}
catch (\Cartalyst\Sentinel\Checkpoints\NotActivatedException $e)
{
$response = ['error' => [$e->getMessage()]];
$httpStatus = 401;
return response()->json($response, $httpStatus);
}
if ($sentinel) //user credentials correct
{
//get oauth token
$oauthToken = Authorizer::issueAccessToken();
$response = ['success' => true, 'user' => ['id' => $sentinel->id, 'email' => $sentinel->email]] + $oauthToken;
$httpStatus = 200;
}
else
{
$response = ['success' => false, 'error' => ['Incorrect credentials']];
$httpStatus = 401;
}
return response()->json($response, $httpStatus);
}
Here is the method the OAuth library looks at to authenticate the user:
public function verifyAuth($email, $password)
{
$credentials = [
'email' => $email,
'password' => $password,
];
if ($user = Sentinel::stateless($credentials))
{
return $user->id;
}
else
{
return false;
}
}
This would create a response like so:
{
"success": true,
"user": {
"id": 1,
"email": "email#domain.tld"
},
"access_token": "6a204bd89f3c8348afd5c77c717a097a",
"token_type": "Bearer",
"expires_in": 28800,
"refresh_token": "092a8e1a7025f700af39e38a638e199b"
}
Hope this helps someone out there
Side Note: I'm sending a POST request to domain.com/auth/authenticate instead of api.domain.com/auth/authenticate because I could not get domain.com/dashboard to recognize sentinel's cookie if I posted to api.domain.com. I've tried changing domain in config/session.php to .domain.com but still nothing. Maybe I'm doing something wrong?
I am using Node.JS with Express, Angular.JS and the node module connect-roles for ACL. I want to allow a user with user.status of "Platinum" to access "Platinum" but not "Gold" and vice versa.
I have the ACL part working, if I enter /Platinum into the navigation bar I can't access /Gold, but when I try to access /Platinum I only get the template but not the root shell, so what comes up is this:
You made it!
You have the {{status}} status!
If I click on a link in angular to /Platinum, everything works as it should. If I enter any neutral address in the navigation bar, everything works as it should.
This should be an easy fix, but I've not figured it out.
Here is the code that sets up authorizations, I'm pretty sure everything here is okay.
ConnectRoles = require('connect-roles')
var user = new ConnectRoles({
failureHandler: function(req, res, action){
var accept = req.headers.accept || '';
res.status(403);
if(accept.indexOf('html')) {
res.render('access-denied', {action: action});
} else {
res.send('Access Denied - You don\'t have permission to: ' + action);
}
}
});
var app = express();
app.use(user.middleware());
// Setting up user authorizations,
// i.e. if req.user.status = "Platinum", they are given Platinum status
user.use('Platinum', function(req) {
if (req.user.status == 'Platinum') {
return true;
}
});
user.use('Gold', function(req) {
if (req.user.status == 'Gold') {
return true;
}
});
user.use('Admin', function(req) {
if (req.user.status == 'Admin') {
return true;
}
});
That sets up authorizations, now the problem lies below with the routing.
app.post('/login', passport.authenticate('local',
{ successRedirect: '/', failureRedirect: '/login' }));
app.get('/Platinum', user.is('Platinum'), function(req, res) {
//Obviously the code below is wrong.
res.render('templates/support/Platinum');
});
app.get('/Gold', user.is('Gold'), function(req, res) {
res.render('templates/support/Gold');
});
The way you are configuring your routes on server side (using express) is not correct. For a single page app like AngularJS, you need to do all of the routing for pages on the client (i.e. in Angular). The server still defines routes for API requests (e.g. getting and posting data) and static resources (index.html, partial HTML files, images, javascript, fonts, etc), though.
Thus the following code is wrong in your server side JS:
app.get('/Platinum', user.is('Platinum'), function(req, res) {
//Obviously the code below is wrong.
res.render('templates/support/Platinum');
});
app.get('/Gold', user.is('Gold'), function(req, res) {
res.render('templates/support/Gold');
});
Just remove those lines.
Instead, you need to define the routes that the server will handle, such as your /login post one first, and how to get static files (I suggest prefixing them all with /pub in the URL). Then you need to do something like the technique in this answer to return your index.html page if no routes are matched.
That way, when a user types http://localhost:port/Gold, express will see there is no route defined for /Gold, so it will return index.html, which will load AngularJS, run your Angular app, which will then look at the URL and see if that matches any of the routes your AngularJS app has configured, and if so, fetch the partial for that page and insert it into your ng-view (if using the core router).