Are you ready to hear about the ultimate flexibility in configuring authentication properties in the Okta Angular SDK? You’ll want to check out this excellent new feature and walk through the steps of adding authentication using Okta to Angular applications.
Configuring authentication properties using Okta in Angular applications
There are three main ways you can add configuration information to Angular applications:
-
Define the value within the app – The easiest, most straightforward route is directly defining the configuration within the
AppModule
orapp.config.ts
. Sure, it’s direct, but it can be limiting since the value can always stay the same. -
Add it into
env
files – You can take the configuration definitions out of the application and pull the values during build time. But this means you’ll need to build separate applications for each environment requiring unique configs. - Populate configuration at runtime – In this pattern, you’d retrieve the configuration in an HTTP call from an API when the app starts and populate the configuration values upon receiving the HTTP response. Runtime configuration means you can change the config values without building and redeploying the Angular application! It’s the ultimate flexibility and the awesome new feature I hinted at in the Okta Angular SDK!
You can read more about these three ways, along with examples, in Three Ways to Configure Modules in Your Angular App.
The best way to experiment with loading authentication configuration at runtime is to try it out yourself. Pull out your machine and warm up your fingers! If you want to jump to the completed project, you can find it in the okta-angular-standalone-config-example GitHub repository.
Otherwise, let’s get coding!
Note
This post is best for developers familiar with Angular. If you are an Angular newbie, start by building your first Angular app using the tutorial created by the Angular team.
Prerequisites
For this tutorial, you will need the following tools:
- Node.js v18 or greater
- Angular CLI
- A web browser with good debugging capabilities
- Your favorite IDE. Still on the hunt? I like VS Code and WebStorm because they have integrated terminal windows.
- Terminal window (if you aren’t using an IDE with a built-in terminal)
Table of Contents
Scaffold an Angular standalone application
Angular introduced standalone components as a developer preview in v14. Since then, the Angular team continued building support for the architecture. It’s now the default when using the Angular CLI in v17, so we’ll use standalone components, functional interceptors, and control flow in this sample.
In the terminal, run the following commands to generate the app and add the components:
npx @angular/cli@17 new okta-angular-example --routing --style scss --defaults
cd okta-angular-example
ng generate component profile --inline-template --inline-style
ng generate component protected --inline-template --inline-style
Open the okta-angular-example
project in your IDE. You’ll first define all the routes for the project. Open the protected
folder and add routes.ts
. We will treat this component as an entrance to a lazy-loaded route. Add the following code to the routes.ts
:
import { Route } from '@angular/router';
import { ProtectedComponent } from './protected.component';
export const PROTECTED_FEATURE_ROUTES: Route[] = [
{ path: '', component: ProtectedComponent }
];
Open app.routes.ts
. Add the following routes:
import { Routes } from '@angular/router';
import { ProfileComponent } from './profile/profile.component';
export const routes: Routes = [
{ path: 'profile', component: ProfileComponent },
{ path: 'protected', loadChildren: () => import('./protected/routes').then(m => m.PROTECTED_FEATURE_ROUTES)},
];
There’s more to do here, but we’ll add it after you add Okta to the project.
Create an Okta OAuth 2.0 and OpenID Connect SPA client
You’ll use Okta to securely handle authentication and authorization for your Angular application.
Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register
to sign up for a new account. If you already have an account, run okta login
. Then, run okta apps create
. Select the default app name, or change it as you see fit. Choose Single-Page App and press Enter.
Use http://localhost:4200/login/callback for the Redirect URI and set the Logout Redirect URI to http://localhost:4200.
What does the Okta CLI do?
The Okta CLI will create an OIDC Single-Page App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. It will also add a trusted origin for http://localhost:4200
. You will see output like the following when it’s finished:
Okta application configuration:
Issuer: https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6
NOTE: You can also use the Okta Admin Console to create your app. See Create an Angular App for more information.
Note the Issuer
and the Client ID
. You’ll need those values for your authentication configuration coming up soon.
There’s one manual change to make in the Okta Admin Console. Add the Refresh Token grant type to your Okta Application. Open a browser tab to sign in to your Okta developer account. Navigate to Applications > Applications and find the Okta Application you created. For Okta Applications using the default name, find the Application named “My SPA.” Otherwise find the Application with your custom name. Select the name to edit the application. Find General Settings section and press the Edit button to add a Grant type. Activate the Refresh Token checkbox and press Save.
We’ll use the Okta Angular and Okta Auth JS libraries to connect our Angular application with Okta authentication. Add them to your project by running the following command:
npm install @okta/[email protected] @okta/[email protected]
Use the Okta Angular SDK for authentication
You’ll start by directly adding the Okta configuration to the application, which is the first and most straightforward method of adding configuration to Angular applications. Open app.config.ts
, and make the following modifications to import the OktaAuthModule
and define the configuration:
import { OktaAuthModule } from '@okta/okta-angular';
import { OktaAuth } from '@okta/okta-auth-js';
export const appConfig: ApplicationConfig = {
providers: [
importProvidersFrom(
OktaAuthModule.forRoot({
oktaAuth: new OktaAuth({
issuer: 'https://{yourOktaDomain}/oauth2/default',
clientId: '{yourClientId}',
redirectUri: `${window.location.origin}/login/callback`,
scopes: ['openid', 'offline_access', 'profile']
})
})
),
provideRouter(routes)
]
};
Don’t forget to replace {yourOktaDomain}
and {yourClientId}
with the values you got from the Okta CLI!
With Okta added to the project, let’s return to the route definitions. You can define the sign-in redirect route and add guards to those routes. Open app.routes.ts
and make the changes so that your route definitions look like this:
import { Routes } from '@angular/router';
import { OktaAuthGuard, OktaCallbackComponent } from '@okta/okta-angular';
import { ProfileComponent } from './profile/profile.component';
export const routes: Routes = [
{ path: 'profile', component: ProfileComponent, canActivate: [OktaAuthGuard] },
{ path: 'protected', loadChildren: () => import('./protected/routes').then(m => m.PROTECTED_FEATURE_ROUTES), canActivate: [OktaAuthGuard] },
{ path: 'login/callback', component: OktaCallbackComponent }
];
You’ll add the code for the components using the Okta SDK to sign in, sign out, and display user claims. We’ll keep the components and their styles minimal so we can focus on the authentication pieces.
Open app.component.html
. Copy the following code into the file.
<div class="toolbar">
@if (isAuthenticated$ | async) {
<div>
<nav class="routes">
<a routerLink="https://dev.to/">Home</a>
<a routerLink="/profile">Profile</a>
<a routerLink="/protected">Protected</a>
</nav>
</div>
<a class="auth" (click)="signOut()">Sign out</a>
} @else {
<a class="auth" (click)="signIn()">Sign in</a>
}
</div>
<p>Learn more about Okta at <a href="https://developer.okta.com">developer.okta.com</a></p>
<router-outlet></router-outlet>
In the app.component.scss
, copy the following code and paste it into the file. No frills here! We are keeping this focused on function, not form.
:host {
font-family: sans-serif;
}
a,
a:visited,
a:hover {
color: #095661;
}
.toolbar {
display: flex;
justify-content: flex-end;
align-items: baseline;
}
.routes,
.routes > a + a {
margin-left: 1rem;
}
.auth {
border: 1px solid #999;
border-radius: 4px;
padding: 4px 8px;
&:hover {
background-color: #2d8c9e;
color: #fff;
}
}
p {
margin: 6rem;
text-align: center;
font-size: large;
}
Finally, we can get to the interesting authentication components. Open app.component.ts
. Since this is a standalone component, you must import the classes it relies on, AsyncPipe
, RouterOutlet
, and RouterLink
.
You’ll use two classes and libraries from Okta, so inject the OktaAuthStateService
and OKTA_AUTH
injection tokens. The OktaAuthStateService.authState$
observable stream emits an authenticated state. You can use this to watch for whether you show the sign-in or sign-out buttons and add the functionality to sign in and sign out using Okta:
import { AsyncPipe } from '@angular/common';
import { Component, inject } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import { OktaAuthStateService, OKTA_AUTH } from '@okta/okta-angular';
import { AuthState } from '@okta/okta-auth-js';
import { filter, map } from 'rxjs';
@Component({
selector: 'app-root',
standalone: true,
imports: [AsyncPipe, RouterOutlet, RouterLink],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
private oktaStateService = inject(OktaAuthStateService);
private oktaAuth = inject(OKTA_AUTH);
public isAuthenticated$ = this.oktaStateService.authState$.pipe(
filter((s: AuthState) => !!s),
map((s: AuthState) => s.isAuthenticated ?? false)
);
public async signIn(): Promise<void> {
await this.oktaAuth.signInWithRedirect();
}
public async signOut(): Promise<void> {
await this.oktaAuth.signOut();
}
}
This code gets you authenticated using Okta. Feel free to start the application and verify you can sign in and out. Serve the application by running the following command in your terminal:
npm start
Note
If you have app initialization errors, double-check your Okta configuration and replace the placeholders.
You should see something like this when you start the application:
The links for the profile and protected routes display after you sign in.
I made no claims about building a beautiful app.
The profile and protected routes display the default text from Angular CLI. It’s time to add something a little more interesting!
Display OpenID Connect claims
The profile should display something about the profile! So, let’s add the code to read one of your standard ID claims and display a warm, friendly greeting to the signed-in user. Open profile.component.ts
. This component uses an inline template and styles.
You’ll add the AsyncPipe
to the imports array and the following template code, replacing the existing template:
<p>You're logged in!
@if(name$ | async; as name) {
<span>Welcome, {{name}} </span>
}
</p>
Time to pretty up the template. Check out the styles, and add this stylistic marvel into the component declaration for the inline styles. Feel free to add some extra flair if you’d like and make this project your own.
p {
text-align: center;
}
To display the claim value, you’ll use the OktaAuthStateService
, which holds your ID token and already decodes the payload for you. That’s pretty handy! You’ll pipe the authState$
observable stream to filter out unauthenticated state and then use the name
claim property. Your component code will look like this:
export class ProfileComponent {
private oktaAuthStateService = inject(OktaAuthStateService);
public name$ = this.oktaAuthStateService.authState$.pipe(
filter((authState: AuthState) => !!authState && !!authState.isAuthenticated),
map((authState: AuthState) => authState.idToken?.claims.name ?? '')
);
}
When you sign in and navigate to the profile route, you’ll see something like this.
With this, you should now have a working app that allows you to sign in, navigate to protected to see there’s nothing there, navigate to profile and feel welcomed, and lastly sign out. Most applications with authentication need an interceptor to add the Authorization
header. Even though we aren’t calling an API, let’s put it in to walk through the steps.
Intercept HTTP requests to add the Authorization header with a functional interceptor
Interceptors manipulate outgoing and incoming HTTP requests, and it’s a fantastic way to automatically add the Authorization
header along with the access token to outgoing calls!
In a new terminal, use Angular CLI to scaffold an interceptor using the following command:
ng generate interceptor auth
Open the auth.interceptor.ts
file to make the required changes. You’ll see Angular CLI created the interceptor function. The Authorization
header contains highly sensitive information, the access token!
**Heads up!** The access token grants the caller permission to access resources, perform actions, and potentially make devastating changes in the name of the user to whom the access token belongs. You don’t want the token to fall into the wrong hands! |
In other words, we must protect the token by sending the token to allowed origins. Change the interceptor code to look like this:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { OKTA_AUTH } from '@okta/okta-angular';
export const authInterceptor: HttpInterceptorFn = (req, next, oktaAuth = inject(OKTA_AUTH)) => {
let request = req;
const allowedOrigins = ['/api'];
const accessToken = oktaAuth.getAccessToken();
if(accessToken && !!allowedOrigins.find(origin => req.url.includes(origin))) {
request = req.clone({ setHeaders: { 'Authorization': `Bearer ${accessToken}` } });
}
return next(request);
};
You’ve completed the interceptor code, but you have to register the interceptor in the application and bring in the HTTP libraries. Open app.config.ts
and add the following code to the providers
array after the provideRouter(routes)
line:
provideHttpClient(withInterceptors([
authInterceptor
]))
When you make HTTP calls to allowed origins, you’ll now see the access token in the header. Feel free to try this out on your own by calling an API such as this sample Express API from Okta, which verifies the access token, too.
We’ve done a lot, but the application still contains the hard-coded Okta configuration. What if you want to load the configuration at runtime from an external source? We can emulate this process!
Add runtime authentication configuration to an Okta Angular app
Stop the application by typing ctrl+c
in the command line. You’ll change the angular.json
file, which requires a restart of the application.
Emulate the response from calling an external API by creating a folder called api
inside the existing src
folder. Then, create a file called config.json
within the api
folder. Your project file structure will look like this:
okta-angular-example
├── src
│ ├── api
│ │ └── config.json
│ ├── app
│ │ └── all the components
│ └── assets, remaining files
└── remaining files
Open config.json
and add the following JSON properties. We’ll use this format for the configuration response you get from calling an external API:
{
"issuer": "https://{yourOktaDomain}.okta.com/oauth2/default",
"clientId": "{yourClientId}"
}
Hey! Those properties and variables look familiar, right? Replace the variables to match what you got from the Okta CLI and what you have in the app.config.ts
file.
The Angular project doesn’t recognize this API path and config file, so if you try serving the application right now, the ng serve
process won’t serve the config.json
file with the app. We must also configure the serve process to include the api
folder. Open angular.json
and find the projects.okta-angular-example.architect.build
section. Add src/api
to the list of assets in the build option:
{
"assets": [
"src/favicon.ico",
"src/assets",
"src/api"
]
}
You can now access the config.json
file at runtime. Feel free to start the application again using npm start
in the terminal. In the browser, navigate to http://localhost:4200/api/config.json
. This route is accessible by your application, and we’ll mimic an HTTP call to read the configuration at runtime.
Open app.config.ts
. Add a function before the export const appConfig
to call the config endpoint for the configuration as shown:
function configInitializer(httpBackend: HttpBackend, configService: OktaAuthConfigService): () => void {
return () =>
new HttpClient(httpBackend)
.get('api/config.json')
.pipe(
tap((authConfig: any) => configService.setConfig({
oktaAuth: new OktaAuth({
...authConfig,
redirectUri: `${window.location.origin}/login/callback`,
scopes: ['openid', 'offline_access', 'profile']
})
})),
take(1)
);
}
Let’s talk through the code. The function takes two parameters – the HttpBackend
class and the OktaAuthConfigService
classes. You first create a new HttpClient
. Notice we’re not using the HttpClient
we can get from Angular dependency injection. We want our auth interceptor to skip this HTTP call. Once you get the config.json
response, you set the OktaAuthConfigService
with those properties.
Next, you can change the ApplicationConfig
, by removing the configuration from the OktaAuthModule
by deleting the .forRoot
method call. You also need this function to run when the application starts. The APP_INITIALIZER
injection token exists precisely for this need. Provide the APP_INITIALIZER
injection token with the configInitializer
function and the necessary dependencies, and add it to the providers array. The appConfig
now looks like this:
export const appConfig: ApplicationConfig = {
providers: [
importProvidersFrom(
OktaAuthModule
),
provideRouter(routes),
provideHttpClient(withInterceptors([
authInterceptor
])),
{ provide: APP_INITIALIZER, useFactory: configInitializer, deps: [HttpBackend, OktaAuthConfigService], multi: true },
]
};
Check it out to verify you can sign in, greet yourself at the profile route, and sign out. Success!
What if you use NgModules architecture?
While this sample demonstrates how to add runtime configuration loading in a standalone application, you can use the same steps in an Angular application with NgModule architecture. Instead, you’ll provide the
APP_INITIALIZER
in theAppModule
providers array.
Use OAuth 2.0, OpenID Connect, and the Okta Angular SDK for secure authentication
In this post, you added runtime Okta configuration in an Angular app using standalone components. Whew! That was a lot of work, but I hope you found it fun and exciting. You can check out the completed application in the okta-angular-standalone-runtime-config-example GitHub repository.
Use Okta Angular SDK for runtime configuration loading in standalone Angular applications
This repository contains a working example of adding Okta Angular SDK to a standalone Angular project and how to load the Okta authentication configuration at runtime. Please read Flexible Authentication Configurations in Angular Applications Using Okta for a detailed guide through.
Prerequisites
- Node 18 or greater
- Okta CLI
- Angular CLI
- Your favorite IDE
- A web browser with good debugging capabilities
- Terminal window
Okta has Authentication and User Management APIs that reduce development time with instant-on, scalable user infrastructure. Okta’s intuitive API and expert support make it easy for developers to authenticate, manage and secure users and roles in any application.
Getting Started
To run this example, run the following commands:
git clone https://github.com/oktadev/okta-angular-standalone-runtime-config-example.git
cd okta-angular-standalone-runtime-config-example
npm ci
Create an OIDC Application in Okta
Create a free developer account with the following command using the Okta CLI:
okta register
If…
Complete code projects include testing. I didn’t walk through the steps of adding unit tests in this post, but you can inspect the completed code project to view and run the tests, too.
Ready to read other interesting posts? Check out the following links.
Don’t forget to follow us on Twitter and subscribe to our YouTube channel for more exciting content. We also want to hear about what tutorials you want to see. Leave us a comment below!