Authentication with React and .NET Core using Okta (openID Connect)
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.
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”
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
ISSUER=https://{ okta-domain }/oauth2/default
CLIENT_ID={ CLIENT_ID okta }
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',
},
};
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;
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();
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
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 ,
{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
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.
{
"$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 )
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Okta": {
"OktaDomain": "https://{ okta-domain }/"
}
}
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();
});
}
}
}
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 )
That Access Token is sent with API calls in order to verify with Server.