Issue publishing ASP.NET Core with React using Identity

Viewed 937

Trying to publish the default ASP.NET Core with React app but with Identity. Here is the MS client app readme enter image description here

This seems to work without identity by publishing directly from visual studio.

But once identity get's added it get's tricky. With identity, we have an additional wwwroot folder in the publish step.

here is the code for the app I am testing. Here is the MS info I used for the identity and auth. This app signs in locally, and in my user secrects I have set up the

{
  "Authentication:Microsoft:ClientId": "IdValue",
  "Authentication:Microsoft:ClientSecret": "SecrectValue"
}

Once published it will throw Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 in the console. But again, not on local.

Folder structure of publish without identity

wwwroot
├── ClinetApp
    ├── build 
        ├── static
            ├── css
            └── js
    ├── asset-manifest.json
    ├── favicon.ico
    ├── index.html
    ├── manifest.json
    ├── precache-manifest.<guid>.js
    └──service-worker.js
├── appsetting.json
├── appsetting.Development.json
├── MS-DLLs
├── App.dll
├── App.exe
├── App.pdb
├── App.deps.json
├── App.runtimeconfig.json
├── App.Views.dll
├── App.Views.pdb
└── web.config //default web config

For deploying to azure, using this blog post (from ClinetApp/ReadMe), another web config needs to be added into wwwroot in addition to moving the ClinetApp/build folder into wwwroot as well. Much like a normal asp.net core app all the static assets are then put into the wwwroot folder and the dll's moved up to the parent folder.

Deploying with out the second web config gives these errors on the left. Errors on the right are after the second web config gets added into the identity proj. ErrorFromWebConfig

Web config for identity project that fixes the two console errors

<?xml version="1.0"?>
<configuration>
    <system.webServer>
<!--This section is for manifest.json not found error -->
    <staticContent>
    <mimeMap fileExtension=".woff2" mimeType="application/x-font-woff" />
    <mimeMap fileExtension=".woff" mimeType="application/x-font-woff" />
    <mimeMap fileExtension=".json" mimeType="application/json" />
    </staticContent>
 <rewrite>
 <rules>
 <!--This section is for "could not load setting for "App" in AuthorizeService.js" error -->
    <rule name="React Routes"  stopProcessing="true">
    <match url=".*" />
    <conditions logicalGrouping="MatchAll">
    <add input="{REQUEST_FILENAME}"    matchType="IsFile" negate="true" />
    <add input="{REQUEST_FILENAME}"    matchType="IsDirectory" negate="true" />
    <add input="{REQUEST_URI}" pattern="^/ (api)" negate="true" />
    </conditions>
    <action type="Rewrite" url="/" />
    </rule>
 </rules>
 </rewrite>
 </system.webServer>
</configuration>

Folder structure of publish with identity

├── wwwroot
    ├── static
        ├── css
        └── js
    ├── asset-manifest.json
    ├── favicon.ico
    ├── index.html
    ├── manifest.json
    ├── precache-manifest.<guid>.js
    ├── //service-worker.js is not used with idenity, see index.html code for explination 
    └── web.config //used for the routing from blog post
├── appsetting.json
├── appsetting.Development.json
├── MS-DLLs
├── App.dll
├── App.exe
├── App.pdb
├── App.deps.json
├── App.runtimeconfig.json
├── App.Views.dll
├── App.Views.pdb
└── web.config //default web config

Once those issues are fixed from the web config it seems that index.html isn't read correctly. every page request gets Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 when serving the index.html. This is using react-router so one of the suggested fixes was

-app.get('/', function (req, res) {
+app.get('/*', function (req, res) {
   res.sendFile(path.join(__dirname, 'build', 'index.html'));
 });

but since this is using react router we aren't calling this code in the solution. Below are the App.js, index.js, and index.html

index.js

import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
//import registerServiceWorker from './registerServiceWorker';

const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const rootElement = document.getElementById('root');

ReactDOM.render(
    <Router basename={baseUrl}>
        <App />
    </Router>,
  rootElement);

// Uncomment the line above that imports the registerServiceWorker function
// and the line below to register the generated service worker.
// By default create-react-app includes a service worker to improve the
// performance of the application by caching static assets. This service
// worker can interfere with the Identity UI, so it is
// disabled by default when Identity is being used.
//
//registerServiceWorker();


Index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <base href="%PUBLIC_URL%/" />
    <!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>CPTest</title>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

App.js

import React, { Component } from 'react';
import { Route } from 'react-router';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import AuthorizeRoute from './components/api-authorization/AuthorizeRoute';
import ApiAuthorizationRoutes from './components/api-authorization/ApiAuthorizationRoutes';
import { ApplicationPaths } from './components/api-authorization/ApiAuthorizationConstants';

import './custom.css'

export default class App extends Component {
  static displayName = App.name;

  render () {
    return (
      <Layout>
        <Route exact path='/' component={Home} />
        <Route path='/counter' component={Counter} />
        <AuthorizeRoute path='/fetch-data' component={FetchData} />
        <Route path={ApplicationPaths.ApiAuthorizationPrefix} component={ApiAuthorizationRoutes} />
      </Layout>
    );
  }
}

In Startup.cs these seem to also be breaking the one without identity when trying to add identity to the project.

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

Dev Tools error for index.html not being json

Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0 enter image description here

So the thing's i've noticed.

  1. Seems to work all in wwwroot if no identity is used
  2. Once identity files need to be moved so only static is in wwwroot and the second webconfig
  3. Still doesn't seem to work because the it is expecting json but getting index.html
  4. The login page is actually a cshtml page. On localhost, react-router redirects to that page. _LoginPartial.cshtml

Publish (left) vs Localhost (right) for login page

Publish vs local host

Publish (left) vs Localhost (right) for counter page (react only)

enter image description here

I have also followed these steps for deploying to prod.

With identity this get's a bit screwy, without it everything works in the azure app service straight from the publish step. Not sure what config needs changed or if anyone has solved this issues another way? Or if something with react router needs to be changed? Any suggestions?

2 Answers

I had the same issue on both on prem server and Azure. I had to install an SSL certificate, on the localhost a Developer cert is used, once you move it to production server you need a SSL cert for Identityserver to work. Also, you can move the DevCert to a production server for testing purposes ONLY (I did this for on-prem, not sure for Azure). I don't recall on the how to do it off hand. I think I exported a .pex or .cer file. I just can't find my notes right now. A quick search should help.

You will need to modify your appsetting.json like so (look under "Key") if you decided to use the localhost development cert

"IdentityServer": {
  "Clients": {
    "AdminPortal": {
      "Profile": "IdentityServerSPA"        
    }
  },
  "Key": {
    "Type": "Store",
    "StoreName": "MY",
    "StoreLocation": "LocalMachine",
    "Name": "CN=localhost"
  }
},

I had the same issue with a couple of solutions I've been working on in my spare time. Finally found a solution that solved it for now. Took the idea from this link. https://github.com/facebook/create-react-app/issues/1910

Problem for me was that the service-worker would grab the request to authentication and instead of redirecting to the appropriate page (login or register) it would just give back a stock 200.

I ended up disabling the service worker by using the following in my index.html page.

import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
//import registerServiceWorker from './registerServiceWorker';
import { unregister } from './registerServiceWorker.js';

const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
const rootElement = document.getElementById('root');

unregister();

ReactDOM.render(
  <BrowserRouter basename={baseUrl}>
    <App />
  </BrowserRouter>,
  rootElement);

With this you can remove the web config rewrite stuff. It's not perfect in that it disables a performance improvement by way of service-worker, but it will get you past the issue of not getting to the login page in Azure.

Related