Use ASP.NET Web API and Angular to Build a Simple App
We learn how to use these popular technologies to create a metrics tracking application, and then secure that application.
Join the DZone community and get the full member experience.
Join For FreeMany .NET developers know and love ASP.NET 4.x, and will continue to build apps with it into the future. ASP.NET 4.X is a battle-tested web framework that has existed for over 15 years and is supported by a mature ecosystem.
On the client-side, many developers use Angular both for home and work projects, and it is outstanding for building enterprise-level, feature rich, applications.
In this tutorial, you will build a simple CRUD app with ASP.NET 4x and Angular to track sugar intake. First, you will learn how to build a REST service with ASP.NET Web API 2. After that, you will implement a SPA that will consume your API. You'll also learn how to easily secure your application.
Make sure you're ready with .NET Framework 4.7.1 and Visual Studio 2017 installed to follow along. Also, you should have Node and npm.
Get Started With ASP.NET
First, let’s create an API using a built-in template within Visual Studio. Let’s start from a scratch.
In Visual Studio, select File -> New Project
At this moment, we have a lot of boilerplate code that we really don’t need in our application. Let’s clean up the boilerplate code and remove all redundant files and NuGet packages. Since we are building an API, we don’t need UI-related files or folders. We can remove all the following:
After you have done this, go to your Global.asax file and remove the using statement:
using System.Web.Optimization;
And also remove the Bundles registration part, since we will not be needing these for API:
BundleConfig.RegisterBundles(BundleTable.Bundles);
We will also remove some of the NuGet packages: Microsoft.ApplicationInsights related packages, Modernizr, WebGrease, Antlr, Bootstrap, jQuery.
You can do it simply by running the following script in your Package Manager Console:
Uninstall-Package Microsoft.ApplicationInsights.Web
Uninstall-Package Microsoft.ApplicationInsights.WindowsServer
Uninstall-Package Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel
Uninstall-Package Microsoft.ApplicationInsights.PerfCounterCollector
Uninstall-Package Microsoft.ApplicationInsights.DependencyCollector
Uninstall-Package Microsoft.ApplicationInsights.Agent.Intercept
Uninstall-Package Microsoft.ApplicationInsights
Uninstall-Package Microsoft.AspNet.Web.Optimization
Uninstall-Package bootstrap
Uninstall-Package jQuery
Uninstall-Package WebGrease
Uninstall-Package Antlr
Uninstall-Package Modernizr
After this is finished, we should have a clean project solution and perfect starting ground. Our solution should now look like the following image:
Install ASP.NET API Project Dependencies
Now, that we have a clean solution let’s install all the necessary packages and project dependencies. Later, we will learn about each of the packages. Again, we will do it using the Package Manager Console by executing the following:
Install-Package Microsoft.Owin.Host.SystemWeb -Version 4.0.0
Install-Package Microsoft.IdentityModel.Protocols.OpenIdConnect -Version 5.2.1
Install-Package Microsoft.IdentityModel.Tokens -Version 5.2.1
Install-Package Microsoft.Owin.Security.Jwt -Version 4.0.0
Install-Package EntityFramework -Version 6.2.0
Install-Package Microsoft.AspNet.WebApi.Cors -Version 5.2.6
Install-Package Microsoft.AspNet.Identity.Owin -Version 2.2.1
Connect With Entity Framework 6
Entity Framework 6 is a mature ORM, built and supported by Microsoft. It allows us to interact with the database without the need to know or execute SQL queries. The classes from our code will map to database tables and will basically define a database structure. EntityFramework has a migration concept, which allows us to track changes within our DB models which reflect database structure.
Let’s now set up our database connection. We will add a connection string to Web.Config. Make sure to add it inside of <configuration>
, after <configSections></configSections>
<connectionStrings>
<add name="OktaConnectionString" providerName="System.Data.SqlClient" connectionString="Server=.;Database=Okta; Integrated Security=True;" />
</connectionStrings>
Create an ASP.NET Model and DbContext
First, we will need a model for tracking sugar levels. Create a file in the Models
folder called SugarLevel.cs
.
using System;
using System.ComponentModel.DataAnnotations;
namespace SugarLevelTracker.Models
{
public class SugarLevel
{
[Key]
public int Id { get; set; }
[Required]
public float Value { get; set; }
[Required]
public string Description { get; set; }
[Required]
public DateTime MeasuredAt { get; set; }
}
}
As you can see, we have standard property for unique identifier - Id - which will be picked up by Entity Framework and used as a primary key. We need Value to store our trackings and Description to associate a name with it. We will also use MeasuredAt
to indicate when we measured our sugar level.
Let’s create our ApplicationDbContext
class, inside of new folder called Data
. It will be pretty simple:
using System.Data.Entity;
using SugarLevelTracker.Models;
namespace SugarLevelTracker.Data
{
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext() :
base("OktaConnectionString")
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
public DbSet<SugarLevel> SugarLevels { get; set; }
}
}
Here, DbContext
refers to the connection string, and contains a property for sugar levels that will map to a table in the database.
Set Up Migrations
First, we will enable migrations. Run the following in Package Manager Console:
Enable-Migrations
Now we can add our initial migration that will contain the creation of the SugarLevel table:
Add-Migration Initial
We can update the database now:
Update-Database -Verbose
The -Verbose
flag will allow us to see the generated SQL statements, and this can help us in resolving errors, if any occur.
Create RESTful ASP.NET API Endpoints
Since we are building a RESTful API for simple CRUD functionality, we will have 5 simple endpoints:
- Get All sugar levels.
- Get sugar level by ID.
- Update an existing sugar level.
- Create new sugar level.
- Delete existing sugar level.
Let’s create a controller that will bring our endpoints to life. We will use scaffolding, a powerful feature that comes with Visual Studio. It will create a controller with CRUD actions, based on our model and DbContext
.
Right-click on the Controllers folder, and choose Add -> Controller:
We will pick a Web API 2 Controller with actions, using Entity Framework. On the next screen, we will choose our model and DbContext
classes, and tell Visual Studio to create async controller actions. Async controller actions will make use of the async/await pattern.
Using the async/await pattern can help us avoid performance bottlenecks and increase the overall responsiveness of our application. The catch with the async/await pattern is that we have to use it on all the methods of all the layers in our application.
Enable CORS for ASP.NET and Angular
Since our client application will be running on a different port than our ASP.NET server, we will need to add CORS middleware to the pipeline.
We will enable CORS globally by adding a following to Register()
method of our WebApiConfig
class, which is located in the App_start
folder.
You will also need to add the using
statement for CORS.
using System.Web.Http.Cors;
Then add the following to the end of the Register()
method.
// Enable CORS for the Angular App
var cors = new EnableCorsAttribute("http://localhost:4200", "*", "*");
config.EnableCors(cors);
Our SPA application will be served by Angular CLI and will be running at port 4200 — we added http://localhost:4200 as the allowed origin. Since we will be running both the front-end and backend locally, we can trust this origin. Therefore, we can use the *
as a wildcard for headers and methods, allowing all headers and methods from this origin. In practice, you would want to only allow truly necessary headers and methods to your clients (origins).
Set a Default Formatter for ASP.NET Web API 2
Sadly, ASP.NET Web API 2, by default, uses an XML formatter. We will remove it and make sure a JSON formatter is used. Add the following code at the end of the Register
method inside of the WebApiConfig
class:
// Set JSON formatter as default one and remove XmlFormatter
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.Remove(config.Formatters.XmlFormatter);
jsonFormatter.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
You’ll also need to add the using
statement for the JSON serializer to the file:
using Newtonsoft.Json.Serialization;
We also want to specify the port the API will be running on so that the Angular application can call it. To do so, right-click on the project in the solution explorer and click properties. In the main properties window, choose Web from the left-hand menu and set the Project URL property to http://localhost:8080
.
Build an SPA Application With Angular
First, let’s begin by using Angular CLI to create our application. Angular CLI is a great tool when it comes to creating Angular apps without spending a considerable amount of time and effort configuring our application. We are not required to be experts at Webpack or waste time configuring project structure, module loaders, environment, and libraries before ever starting to write any code. Angular CLI does all of this for us, so we can focus on building SPA applications. For more information on Angular CLI you can check their documentation.
First, we need to install Angular CLI globally by running the following command within Powershell or Command Prompt:
npm install -g @angular/cli
If you right click on the solution explorer, and choose command prompt from the context menu, make sure you are in the SugarLevelTracker
folder and run :
ng new sugar-level-tracker --directory Angular
This command will create an Angular
directory in root folder of project, and inside of that Angular
directory the Angular CLI will create the Angular app.
We can bootstrap our app by running following command:
cd Angular
ng serve
Now we can start building our SPA.
First, let’s install the following packages: Angular Material, Angular CDK, Angular Date Time Picker, RxJS Compat.
npm install @angular/material @angular/cdk ng-pick-datetime rxjs-compat --save
If the installation went well, we should see packages in package.json dependencies list.
To bring in the styles for Angular Material and the Owl DateTime Picker, put these two statements in the styles.css
in the root of the Angular application folder:
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
@import '~ng-pick-datetime/assets/style/picker.min.css';
Our app contains only one root module, AppModule
, and one component, AppComponent
. The application we are making is a very simple one. Hence, we will use the existing AppModule
. If, later, our application gets more complex we can introduce features, routing, and shared modules to better organize our code. Since our application will be able to track sugar levels we will need to create an appropriate service, SugarLevelService
, and components for listing and editing the sugar levels.
It would be nice to also create a TypeScript model for sugar levels, to make it easier for us to work with sugar level objects. Let’s first create a folder (shared
) inside of our app
folder, which is part of the Angular application that gets created by Angular CLI. Inside of the shared
folder create a new folder called models
, and this is where we will create the SugarLevel.ts
file:
export default class SugarLevel {
id: number;
value: number;
description: string;
measuredAt: string;
}
After this, let’s create an api
folder inside of the shared
folder, and inside of it a new file called sugar-level.service.ts
:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import SugarLevel from '../models/SugarLevel';
@Injectable()
export default class SugarLevelService {
public API = 'http://localhost:8080/api';
public SUGARLEVELS_API = `${this.API}/sugarlevels`;
constructor(private http: HttpClient) {}
getAll(): Observable<Array<SugarLevel>> {
return this.http.get<Array<SugarLevel>>(this.SUGARLEVELS_API);
}
get(id: string) {
return this.http.get(`${this.SUGARLEVELS_API}/${id}`);
}
save(sugarLevel: SugarLevel): Observable<SugarLevel> {
let result: Observable<SugarLevel>;
if (sugarLevel.id) {
result = this.http.put<SugarLevel>(
`${this.SUGARLEVELS_API}/${sugarLevel.id}`,
sugarLevel
);
} else {
result = this.http.post<SugarLevel>(this.SUGARLEVELS_API, sugarLevel);
}
return result;
}
remove(id: number) {
return this.http.delete(`${this.SUGARLEVELS_API}/${id.toString()}`);
}
}
Now we can add sugarlevel-list.component.ts
. You can easily scaffold the component using the Angular CLI, by opening a command prompt in the Angular/src/app
folder and running:
ng g c sugarlevel-list
This tells the Angular CLI (ng
) to generate (g
) a component (c
) called sugarlevel-list
. This will generate a folder called sugarlevel-list
with four files in it:
sugarlevel-list.component.css
sugarlevel-list.component.html
sugarlevel-list.component.spec
sugarlevel-list.component.ts
The Typescript file is the one we care about at the moment. The contents of the sugarlevel-list.component.ts
file should be replaced with:
import { Component, OnInit } from '@angular/core';
import SugarLevelService from '../shared/api/sugar-level.service';
import SugarLevel from '../shared/models/SugarLevel';
@Component({
selector: 'app-sugarlevel-list',
templateUrl: './sugarlevel-list.component.html',
styleUrls: ['./sugarlevel-list.component.css']
})
export class SugarLevelListComponent implements OnInit {
sugarLevels: Array<SugarLevel>;
constructor(private sugarLevelService: SugarLevelService) {}
ngOnInit() {
this.sugarLevelService.getAll().subscribe(data => {
this.sugarLevels = data;
});
}
}
And the code for sugarlevel-list.component.html
:
<mat-card>
<mat-card-header>Sugar Level List</mat-card-header>
<mat-card-content>
<mat-list>
<mat-list-item *ngFor="let sugarLevel of sugarLevels">
<h3 mat-line>
<a mat-button [routerLink]="['/sugarlevel-edit', sugarLevel.id]"> - </a>
</h3>
</mat-list-item>
</mat-list>
</mat-card-content>
<button mat-fab color="primary" [routerLink]="['/sugarlevel-add']">Add</button>
</mat-card>
We can now use the same command that we used to generate the list component to create and edit component:
ng g c sugarlevel-edit
Replace the code in sugarlevel-edit.component.ts
with:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { ActivatedRoute, Router } from '@angular/router';
import { NgForm } from '@angular/forms';
import SugarLevelService from '../shared/api/sugar-level.service';
import SugarLevel from '../shared/models/SugarLevel';
@Component({
selector: 'app-sugarlevel-edit',
templateUrl: './sugarlevel-edit.component.html',
styleUrls: ['./sugarlevel-edit.component.css']
})
export class SugarLevelEditComponent implements OnInit, OnDestroy {
sugarLevel: SugarLevel = new SugarLevel();
sub: Subscription;
constructor(
private route: ActivatedRoute,
private router: Router,
private sugarLevelService: SugarLevelService
) {}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
const id = params['id'];
if (id) {
this.sugarLevelService.get(id).subscribe((sugarLevel: any) => {
if (sugarLevel) {
this.sugarLevel = sugarLevel;
this.sugarLevel.measuredAt = new Date(
this.sugarLevel.measuredAt
).toISOString();
} else {
console.log(
`Sugar Level with id '${id}' not found, returning to list`
);
this.gotoList();
}
});
}
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
gotoList() {
this.router.navigate(['/sugarlevel-list']);
}
save(form: any) {
this.sugarLevelService.save(form).subscribe(
result => {
this.gotoList();
},
error => console.error(error)
);
}
remove(id: number) {
this.sugarLevelService.remove(id).subscribe(
result => {
this.gotoList();
},
error => console.error(error)
);
}
}
Code for sugarlevel-edit.component.html
:
<mat-card>
<form #sugarLevelForm="ngForm" (ngSubmit)="save(sugarLevelForm.value)">
<mat-card-header>
<mat-card-title>
<h2>{{sugarLevel.value ? 'Edit' : 'Add'}} Sugar Level</h2>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<input type="hidden" name="id" [(ngModel)]="sugarLevel.id">
<mat-form-field>
<input matInput placeholder="Sugar Level Description" [(ngModel)]="sugarLevel.description" required name="description">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Sugar Level Value" [(ngModel)]="sugarLevel.value" required name="value">
</mat-form-field>
<mat-form-field class="col-xs-12">
<input matInput [(ngModel)]="sugarLevel.measuredAt" [owlDateTime]="dt2" [owlDateTimeTrigger]="dt2" placeholder="Measured At"
name="measuredAt" required>
<owl-date-time #dt2></owl-date-time>
<mat-error>
Required
</mat-error>
</mat-form-field>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="primary" type="submit" [disabled]="!sugarLevelForm.form.valid">Save
</button>
<button mat-raised-button color="secondary" (click)="remove(sugarLevel.id)" *ngIf="sugarLevel.id" type="button">Delete
</button>
<a mat-button routerLink="/sugarlevel-list">Cancel</a>
</mat-card-actions>
<mat-card-footer>
</mat-card-footer>
</form>
</mat-card>
After adding components we will need to navigate between them by implementing routing. First, we will import the RouterModule
module and Routes
type. Then we will create a route configuration of Routes type, and after, we will register RouterModule
with route configuration on the root level.
Inside of app.module.ts
, let’s add the routing configuration right at the top, below the import statements:
const appRoutes: Routes = [
{ path: '', redirectTo: '/sugarlevel-list', pathMatch: 'full' },
{
path: 'sugarlevel-list',
component: SugarLevelListComponent
},
{
path: 'sugarlevel-add',
component: SugarLevelEditComponent
},
{
path: 'sugarlevel-edit/:id',
component: SugarLevelEditComponent
}
];
Inside of imports
array, we will add the following:
RouterModule.forRoot(appRoutes)
You’ll also need to import some components from the @angular/router
, @angular/material
and the ng-pick-datetime
packages for the HTML files that were added.
This is how the app.module.ts
file should look after all this:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import {
MatButtonModule,
MatCardModule,
MatInputModule,
MatListModule,
MatToolbarModule
} from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Routes, RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
import { AppComponent } from './app.component';
import { SugarLevelListComponent } from './sugarlevel-list/sugarlevel-list.component';
import { SugarLevelEditComponent } from './sugarlevel-edit/sugarlevel-edit.component';
import SugarLevelService from './shared/api/sugar-level.service';
const appRoutes: Routes = [
{ path: '', redirectTo: '/sugarlevel-list', pathMatch: 'full' },
{
path: 'sugarlevel-list',
component: SugarLevelListComponent
},
{
path: 'sugarlevel-add',
component: SugarLevelEditComponent
},
{
path: 'sugarlevel-edit/:id',
component: SugarLevelEditComponent
}
];
@NgModule({
declarations: [
AppComponent,
SugarLevelListComponent,
SugarLevelEditComponent
],
imports: [
BrowserModule,
HttpClientModule,
MatButtonModule,
MatCardModule,
MatInputModule,
MatListModule,
MatToolbarModule,
BrowserAnimationsModule,
FormsModule,
RouterModule.forRoot(appRoutes),
OwlDateTimeModule,
OwlNativeDateTimeModule
],
providers: [SugarLevelService],
bootstrap: [AppComponent]
})
export class AppModule {}
We will use the service, SugarLevelService
, to enable our components to communicate with our REST API. Also, it’s important to note that Angular HttpClient uses RxJS observables instead of promises, so the sooner we learn RxJS the better. SugarLevelService
is registered in the AppModule
, as a singleton service and is available everywhere across our app.
Create an Okta Application
Dealing with user authentication in web apps is a massive pain for every developer. This is where Okta shines: it helps you secure your web applications with minimal effort. To get started, you’ll need to create an OpenID Connect application in Okta. Sign up for a forever-free developer account (or log in if you already have one).
Once you’ve logged in and landed on the dashboard page, copy down the Org URL pictured below. You will need this later.
Then create a new application by browsing to the Applications tab and clicking Add Application, and from the first page of the wizard choose Single-Page App.
On the settings page, enter the following values:
Name: AngularCrudApp
Base URIs: http://localhost:4200
- Login redirect URIs: http://localhost:4200/implicit/callback
You can leave the other values unchanged, and click Done.
Now that your application has been created, copy down the Client ID and Client secret values on the following page, you’ll need them soon (of course, yours will be different).
Secure Your ASP.NET Core + Angular Application
If you don’t already have a Startup.cs file (OWIN Startup class), create one by right-clicking on your project and choosing Add - Class. Pick the OWIN Startup template and name the new class Startup.
Make sure you have these using statements at the top of your Startup.cs file:
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Owin;
using System.Threading.Tasks;
Add the following code to your Configuration method:
public void Configuration(IAppBuilder app)
{
// Configure JWT Bearer middleware
// with an OpenID Connect Authority
var authority = "https://okta.okta.com/oauth2/default";
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
authority + "/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "api://default",
ValidIssuer = authority,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
var discoveryDocument = Task.Run(() => configurationManager.GetConfigurationAsync()).GetAwaiter().GetResult();
return discoveryDocument.SigningKeys;
}
}
});
}
Protect Your ASP.NET API Endpoints
Now it’s time to protect all our endpoints for the SugarLevel
controller. Since we want to protect all actions we will simply apply the Authorize attribute at the controller level. We could also apply the attribute to specific actions, or even globally for all controllers and actions. If we applied it globally, we could exclude Authorize on specific actions and controllers by using the AllowAnonymous
attribute.
Implement Authentication in Angular
To add authentication to our Angular app we will use Okta’s Angular SDK.
npm install @okta/okta-angular --save
In the app.module.ts
file, add a config variable right below the appRoutes
for the application.
const config = {
issuer: 'https://okta.okta.com/oauth2/default',
redirectUri: 'http://localhost:4200/implicit/callback',
clientId: '{clientId}'
};
The OktaAuthModule will use that configuration. Import the module first:
import { OktaAuthModule } from '@okta/okta-angular';
Then add the initialization to the imports array.
OktaAuthModule.initAuth(config);
We will also create AuthInterceptor
and add it to the AppModule
providers array. AuthInterceptor
is used to append the authorization headers to our requests.
Inside of our shared
folder we will create a new folder called interceptors
, and there we will create our auth.interceptor.ts
file:
import { Injectable } from '@angular/core';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/fromPromise';
import { OktaAuthService } from '@okta/okta-angular';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private oktaAuth: OktaAuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return Observable.fromPromise(this.handleAccess(request, next));
}
private async handleAccess(
request: HttpRequest<any>,
next: HttpHandler
): Promise<HttpEvent<any>> {
const accessToken = await this.oktaAuth.getAccessToken();
request = request.clone({
setHeaders: {
Authorization: 'Bearer ' + accessToken
}
});
return next.handle(request).toPromise();
}
}
Then wire up the interceptor to intercept requests as they go out by adding it to the app module so that the import for common http looks like:
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
And then add the interceptor to the providers.
On AppComponent
and HomeComponent
initialization we use Okta’s authentication state and provide a callback that will be executed any time state is updated. If our app isn’t authenticated, HomeComponent
will render a Login button with a link to the redirect URL for us to authenticate. If the user is authenticated, our HomeComponent
will redirect the user to SugarLevelListComponent
.
Generate the home component as before:
ng g c home
Then add the code for home.component.ts
:
import { Component, OnInit } from '@angular/core';
import { OktaAuthService } from '@okta/okta-angular';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
isAuthenticated: boolean;
constructor(private oktaAuth: OktaAuthService) {}
async ngOnInit() {
this.isAuthenticated = await this.oktaAuth.isAuthenticated();
// Subscribe to authentication state changes
this.oktaAuth.$authenticationState.subscribe(
(isAuthenticated: boolean) => (this.isAuthenticated = isAuthenticated)
);
}
}
Also, the code for home.component.html
:
<mat-card>
<mat-card-content>
<button mat-raised-button color="warn" *ngIf="!isAuthenticated"
(click)="oktaAuth.loginRedirect()">
Login
</button>
<button mat-raised-button color="warn" *ngIf="isAuthenticated"
[routerLink]="['/sugarlevel-list']">
Sugar Level List
</button>
<button mat-raised-button color="warn" *ngIf="isAuthenticated"
(click)="oktaAuth.logout()">
Login
</button>
</mat-card-content>
</mat-card>
Replace all the code in the app.component.html
with this:
<router-outlet></router-outlet>
This tells Angular where to put the output from the components that are handling routing.
Then replace the component handling the ‘’
path with the home component in the routing array in app.component.ts
:
{ path: '', component: HomeComponent, pathMatch: 'full' },
Also, import the OktaCallbackComponent
from Okta’s Angular SDK and add the callback route to the app component:
{ path: 'implicit/callback', component: OktaCallbackComponent }
So the final app.module.ts
looks like:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import {
MatButtonModule,
MatCardModule,
MatInputModule,
MatListModule,
MatToolbarModule
} from '@angular/material';
import { Routes, RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
import { OktaAuthModule, OktaCallbackComponent } from '@okta/okta-angular';
import { AppComponent } from './app.component';
import { SugarLevelListComponent } from './sugarlevel-list/sugarlevel-list.component';
import { SugarLevelEditComponent } from './sugarlevel-edit/sugarlevel-edit.component';
import SugarLevelService from './shared/api/sugar-level.service';
import { HomeComponent } from './home/home.component';
import { AuthInterceptor } from './shared/interceptors/auth.interceptor';
const appRoutes: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{
path: 'sugarlevel-list',
component: SugarLevelListComponent
},
{
path: 'sugarlevel-add',
component: SugarLevelEditComponent
},
{
path: 'sugarlevel-edit/:id',
component: SugarLevelEditComponent
},
{ path: 'implicit/callback', component: OktaCallbackComponent }
];
const config = {
issuer: 'https://okta.okta.com/oauth2/default',
redirectUri: 'http://localhost:4200/implicit/callback',
clientId: '{clientId}'
};
@NgModule({
declarations: [
AppComponent,
SugarLevelListComponent,
SugarLevelEditComponent,
HomeComponent
],
imports: [
BrowserModule,
HttpClientModule,
MatButtonModule,
MatCardModule,
MatInputModule,
MatListModule,
MatToolbarModule,
FormsModule,
RouterModule.forRoot(appRoutes),
OwlDateTimeModule,
OwlNativeDateTimeModule,
OktaAuthModule.initAuth(config)
],
providers: [
SugarLevelService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule {}
By using Okta to handle authorization we don’t need to keep auth state ourselves, which makes building app much simpler.
And now you’re done! You have a secure ASP.NET Web API with an Angular front-end to track sugar levels (or whatever else you might want to track!). The complete and working sample that backs this tutorial is available on GitHub as SugarLevelTracker
Learn More About ASP.NET Core and Angular
If you enjoyed building this ASP.NET Core API with Angular, check out more full-stack CRUD posts from Okta.
As always, if you have any questions, comments, or concerns about this post feel free to leave a comment below. For other great content from the Okta Dev Team, follow us on Twitter @OktaDev, Facebook, and watch us on YouTube!
Build a CRUD App with ASP.NET Framework 4.x Web API and Angular was originally published to the Okta developer blog on July 27, 2018.
Published at DZone with permission of Lee Brandt, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments