Authentication with React and .NET Core using Okta (openID Connect)

Ankit Khandeparkar
8 min readMay 14, 2021

Authentication on the go!!

Nowadays, it is common to have a “back-end” and a “front-end” so that two (or more) teams can work on a project. The latest version of Microsoft’s .NET Core is powerful and a cross-platform tool. Using it with Facebook’s React framework makes it a pretty stable platform to develop web applications. In this tutorial I will demonstrate how to add authentication using open id connect for React and .NET Core.

React verifies user with okta, Okta redirects back with a code, This is then shared by .NetCore which then is verified by okta.
Open Id Connect Flow for React and .NET Core

User Authentication in web app can be a pain and time consuming this is where Okta comes in and makes is easy to secure your web applications with minimal effort.

What you’ll need to get started,

.NET Core3.1, Node and npm, Visual Studio or VS Code

Set up Your Okta Application

To get going, you’ll need to create an OpenID connect application in Okta. Sign up for a free developer account or login if you already have one.

Then create a new application by browsing to the “Applications” tab in the left. Select “Applications” and click “Add Application”

Click Create New App
From the wizard, choose the “Single-Page App” option for the React app.
and click “Create”
In “General Settings”, enter the following values, and click “Save”

Name: OpenID-React
-Login redirect URIs: http://localhost:3000/login/callback
-Logout redirect URIs: http://localhost:3000/

Now that your application has been created, copy down the Client ID and Okta domain on the following page, you’ll need them soon.

Make Sure General setting are changed refer below image.

Now, you need to assign users to this app in order to login,
Click on “Assignments”, “Assign”, “Assign to People”

Time to start with react

I’m creating a new app you can continue with your exsisting project but you need to install some libraries

Commands

npx create-react-app okta-tutorialnpm install @okta/okta-signin-widgetnpm install @okta/okta-auth-js @okta/okta-react react-router-domnpm install dotenv — savenpm install semantic-ui-react semantic-ui-css
Create a “.okta.env” file in root directory
Add the following and fill the domain as ISSUER and client id
ISSUER=https://{ okta-domain }/oauth2/default
CLIENT_ID={ CLIENT_ID okta }
Create config.js file in directory src/config.js Add the following code
const CLIENT_ID = process.env.CLIENT_ID || 'Client id okta ';
const ISSUER = process.env.ISSUER || 'https://{ okta-domain }/oauth2/default';
const OKTA_TESTING_DISABLEHTTPSCHECK = process.env.OKTA_TESTING_DISABLEHTTPSCHECK || false;
const REDIRECT_URI = `${window.location.origin}/login/callback`;

// eslint-disable-next-line
export default {
oidc: {
clientId: CLIENT_ID,
issuer: ISSUER,
redirectUri: REDIRECT_URI,
scopes: ['openid', 'profile', 'email'],
pkce: true,
disableHttpsCheck: OKTA_TESTING_DISABLEHTTPSCHECK,
},
resourceServer: {
messagesUrl: 'http://localhost:8000/api/messages',
},
};
Edit App.js component in file directory src/App.js Add the following code
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';
import { Security, SecureRoute, LoginCallback } from '@okta/okta-react';
import { Route, Switch, useHistory } from 'react-router-dom';
import { Container } from 'semantic-ui-react';
import config from './config';
import './App.css';
import Home from './Home';
import Messages from './Messages';
import Navbar from './Navbar';

const oktaAuth = new OktaAuth(config.oidc);

function App() {

const history = useHistory();
const restoreOriginalUri = async (oktaAuth, originalUri) => {
history.replace(toRelativeUrl(originalUri, window.location.origin));
};

return (
<Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
<Navbar />
<Container text style={{marginTop: '7em'}}>
<Switch>
<Route path="/" exact={true} component={Home}/>
<Route path="/login/callback" component={LoginCallback}/>
<SecureRoute path="/messages" component={Messages}/>
</Switch>
</Container>
</Security>
);
}

export default App;
Edit Index.js component in file directory src/Index.js Add the following code
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import 'semantic-ui-css/semantic.min.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);

reportWebVitals();
Create Navbar.js component in file directory src/Navbar.js Add the following code
import { useOktaAuth } from '@okta/okta-react';
import { Link } from 'react-router-dom';
import { Container, Icon, Menu } from 'semantic-ui-react';
import React from 'react'

const Navbar = () => {
const { authState, oktaAuth } = useOktaAuth();

const login = async () => oktaAuth.signInWithRedirect();
const logout = async () => oktaAuth.signOut();
return (
<div>
<Menu fixed="top" inverted>
<Container>
<Menu.Item header>
<Link to="/">Okta-React Project</Link>
</Menu.Item>
{authState.isAuthenticated && (
<Menu.Item id="messages-button">
<Icon name="mail outline" />
<Link to="/messages">Messages</Link>
</Menu.Item>
)}
{authState.isAuthenticated && (
<Menu.Item id="logout-button" onClick={logout}>Logout</Menu.Item>
)}
{!authState.isPending && !authState.isAuthenticated && (
<Menu.Item onClick={login}>Login</Menu.Item>
)}
</Container>
</Menu>
</div>
)
}
export default Navbar
Create Home.js component in file directory src/Home.js Add the following code
import { useOktaAuth } from '@okta/okta-react';
import React, { useState, useEffect } from 'react';

const Home = () => {
const { authState, oktaAuth } = useOktaAuth();
const [userInfo, setUserInfo] = useState(null);

useEffect(() => {
if (!authState.isAuthenticated) {
// When user isn't authenticated, forget any user info
setUserInfo(null);
} else {
oktaAuth.getUser().then((info) => {
setUserInfo(info);
});
}
}, [authState, oktaAuth]); // Update if authState changes
return (
<div>

Home Page

{authState.isAuthenticated && userInfo
&& (
<div>
<p>
Welcome ,&nbsp;
{userInfo.name}
!
</p>
<p>
Succesfully Logged In at Client Side using Open Id Connect
</p>
<h5>Access Token is</h5>
<h5>{authState.accessToken.accessToken}</h5>
</div>
)}

</div>
)
}

export default Home
Create Messages.js component in file directory src/Messages.js Add the following code
import { useOktaAuth } from '@okta/okta-react';
import React, { useState, useEffect } from 'react';
import { Header, Icon, Message, Table, Container } from 'semantic-ui-react';
import config from './config';

const Messages = () => {
const { authState, oktaAuth } = useOktaAuth();
const [messages, setMessages] = useState(null);
const [messageFetchFailed, setMessageFetchFailed] = useState(false);

useEffect(() => {
if (authState.isAuthenticated) {
const accessToken = oktaAuth.getAccessToken();
fetch(config.resourceServer.messagesUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
if (!response.ok) {
return Promise.reject();
}
return response.json();
})
.then((data) => {
let index = 0;
const formattedMessages = data.messages.map((message) => {
const date = new Date(message.date);
const day = date.toLocaleDateString();
const time = date.toLocaleTimeString();
index += 1;
return {
date: `${day} ${time}`,
text: message.text,
id: `message-${index}`,
};
});
setMessages(formattedMessages);
setMessageFetchFailed(false);
})
.catch((err) => {
setMessageFetchFailed(true);
console.error(err);
});
}
}, [authState, oktaAuth]);

const possibleErrors = [
'Server is not responding',
'Or Server may not be authorized'
];

return (
<Container text style={{marginTop: '7em'}}>
<Header as="h1">
<Icon name="mail outline" />
My Messages
</Header>
{messageFetchFailed && <Message error header="Failed to fetch messages. Please verify the following:" list={possibleErrors} />}
{!messages && !messageFetchFailed &&
<div className="ui active dimmer">
<div className="ui large text loader">Loading Messages</div>
</div>}
{messages
&& ( <div>
<Table>
<thead>
<tr>
<th>Date</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{messages.map((message) => (
<tr id={message.id} key={message.id}>
<td>{message.date}</td>
<td>{message.text}</td>
</tr>
))}
</tbody>
</Table>
</div>
)}
</Container>
);
};

export default Messages;

Your Client is All set and Ready to go,
Now the sever part begins

If you are using .NET CLI then, open cmd and type this will create a new webapi project.

dotnet new webapi -n okta-Server

If you areusing Visual Studio then create it with GUI.

Go to Properties/launchSettings.json and make following changes
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8000/"
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"launchUrl": "http://localhost:8000/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"okta_Server": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:8000/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

NOTE: If no changes then make sure your messagesUrl in React okta is also set to the server url.

Now you need to install Nuget Package “Okta.AspNetCore” (use any nuget Package Manager for this )

In appsettings.json add the okta domain required
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Okta": {
"OktaDomain": "https://{ okta-domain }/"
}
}
After installing the latest version you need to make changes in the Startup.cs file
Add the following code in “ConfiguringServices” method and before “services.AddControllers()”
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Okta.AspNetCore;

namespace okta_Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
// The CORS policy is open for testing purposes. In a production application, you should restrict it to known origins.
options.AddPolicy(
"AllowAll",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
})
.AddOktaWebApi(new OktaWebApiOptions()
{
OktaDomain = Configuration["Okta:OktaDomain"],
});

services.AddAuthorization();

services.AddControllers();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors("AllowAll");

app.UseAuthentication();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
After that in Configure method Add the code in similar manned as in above code.

Create a MessageController.cs inside Controller/
And add the following code if there is an exsisting controller do the necessary changes as below.

using System;
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace okta_Server.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class MessageController : ControllerBase
{
[Authorize]
[HttpGet]
[Route("~/api/messages")]
[EnableCors("AllowAll")]

public JsonResult Get()
{
var principal = HttpContext.User.Identity as ClaimsIdentity;

var login = principal.Claims
.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)
?.Value;

return new JsonResult(new
{
messages = new dynamic[]
{
new { Date = DateTime.Now, Text = "Sending this Message from Server" },
new { Date = DateTime.Now, Text = "Verified the access Token" },
new { Date = DateTime.Now, Text = "You are now Authenicated by okta" },
new { Date = DateTime.Now, Text = "I hope this was helpful" },
},
});
}
}
}

Done!!

Now run the React and Dotnet and it should look something like below.

Commands to Run

dotnet watch run          ( inside .net folder )npm start                 ( inside React folder )
The home Page notice there is only login option
After Clicking on Login you redirect to Okta page
Now that you are Logged in you can see Messages and a Logout tab

That Access Token is sent with API calls in order to verify with Server.

Messages Sent Back From the Server to The Client.

--

--

Ankit Khandeparkar

I am a Software Developer, passionate about Embedded systems by making various projects. Other than that I love to play Football and Badminton.