Franck on Unsplash

How to secure Angular applications using Gravitee Access Management

Julien Giovaresco
graviteeio
Published in
5 min readJul 12, 2022

--

Introduction

I recently needed to secure an Angular application. Up to now, Gravitee Cockpit implements its authentication logic, and as we have decided to move to SaaS only, we want to replace this logic by plugging in our Gravitee Access Management platform. In addition to the “eating your own dog food” principle, this brings the following benefits:

  • Isolating user PII in one place
  • Adding more security (two-factor authentication (2FA))
  • Easing the integration of new a identity provider

This blog post will describe the required steps to secure an Angular application using Gravitee Access Management.

Background

Angular is a TypeScript-based open source web application framework. It is commonly used for building single-page mobile and desktop web applications.

As with all applications, depending on the use case, you may decide to restrict access to it. This could be for a number of reasons, such as:

  • Limiting access to data
  • Managing different user views and experiences
  • Offering a commercial service

There are a number of ways to secure an application, for example, by managing the login process directly from it. The challenges with this approach come from managing all the different types of login options. For example, by user name and password, single sign-on, biometrics, and so forth. Every time you need to add a new authentication method, or update an existing one results in making direct changes to the application itself.

Another approach that can be used is through an Identity and Access Management (IAM) platform. This allows you to decouple authentication logic away from your application, leaving the focus on the business logic within the application itself. The implementation involves creating an integration with the application and the IAM, and then all of the different forms of login options are then managed and mediated by the access manager itself.

Gravitee Access Management (AM) is a flexible, lightweight and easy-to-use open-source Identity and Access Management solution. It offers a centralized authentication and authorization service to deliver secure access for authorized users to your applications and APIs from any device.

Let’s take a look at how we can get an Angular application working with AM for managing the login.

Initializing Gravitee Access Management

We will start Gravitee AM using the Docker Compose image. You can take a look at the README from the example repository.

We will need to set up a Single Page Application (SPA). If you are not familiar with Gravitee AM, you can find out more from our Quick Start Guide.

We have provided a script to configure everything in AM for your convenience. Using the AM APIs, this script will do the following:

  • Initialize an inline Identity Provider
  • Allow localhost & HTTP redirect URI (in Settings > Client Registration)
  • Configure the redirect URI (in Application > Settings > General > Redirect URIs)
  • Grant Refresh Token flow (in Application > Settings > OAuth 2.0 / OIDC > Grant flows)
  • Configure openid, profile, email scope (in Application > Settings > OAuth 2.0 / OIDC > Scopes)

Example of the output of the initialization script:

❯ ./initialization.sh
Update the entrypoint value
{"id":"7fff860f-787b-4ff8-bf86-0f787bdff867","name":"Default","description":"Default entrypoint","url":"http://localhost/am","tags":[],"organizationId":"DEFAULT","defaultEntrypoint":true,"createdAt":1657545287078,"updatedAt":1657545314853}
Create the domainEnable the domain
{"id":"b0bda1cf-a836-45ab-bda1-cfa83685ab3b","hrid":"local","name":"Local","description":"My First Security Domain description","referenceType":"environment","referenceId":"DEFAULT","enabled":true,"alertEnabled":false,"path":"/local","master":false,"vhostMode":false,"createdAt":1657545314894,"updatedAt":1657545315504,"oidc":{"clientRegistrationSettings":{"allowLocalhostRedirectUri":false,"allowHttpSchemeRedirectUri":false,"allowWildCardRedirectUri":false,"isDynamicClientRegistrationEnabled":false,"isOpenDynamicClientRegistrationEnabled":false,"isAllowedScopesEnabled":false,"isClientTemplateEnabled":false},"securityProfileSettings":{"enablePlainFapi":false,"enableFapiBrazil":false},"redirectUriStrictMatching":false,"cibaSettings":{"enabled":false,"authReqExpiry":600,"tokenReqInterval":5,"bindingMessageLength":256}}}
Allow localhost & http redirect uri
{"id":"b0bda1cf-a836-45ab-bda1-cfa83685ab3b","hrid":"local","name":"Local","description":"My First Security Domain description","referenceType":"environment","referenceId":"DEFAULT","enabled":true,"alertEnabled":false,"path":"/local","master":false,"vhostMode":false,"createdAt":1657545314894,"updatedAt":1657545315550,"oidc":{"clientRegistrationSettings":{"allowLocalhostRedirectUri":true,"allowHttpSchemeRedirectUri":true,"allowWildCardRedirectUri":false,"isDynamicClientRegistrationEnabled":false,"isOpenDynamicClientRegistrationEnabled":false,"isAllowedScopesEnabled":false,"isClientTemplateEnabled":false},"securityProfileSettings":{"enablePlainFapi":false,"enableFapiBrazil":false},"redirectUriStrictMatching":false,"cibaSettings":{"enabled":false,"authReqExpiry":600,"tokenReqInterval":5,"bindingMessageLength":256}}}
create inline providercreate an applicationConfigure application
{"id":"c9001afd-4632-47e8-801a-fd463247e88f","name":"example","type":"browser","domain":"b0bda1cf-a836-45ab-bda1-cfa83685ab3b","enabled":true,"template":false,"certificate":"f4eb1be1-1091-45dc-ab1b-e11091c5dc09","settings":{"oauth":{"clientId":"bc94b5a6-7b66-4440-94b5-a67b66e440ae","clientSecret":"-fT60EcrrLHdkdWYGbPLxcDm90M9LtGVxwlKL0YPloA","clientType":"public","redirectUris":["http://localhost:4200"],"responseTypes":["code","code id_token token","code id_token","code token"],"grantTypes":["authorization_code","refresh_token"],"applicationType":"web","clientName":"example","tokenEndpointAuthMethod":"none","requireAuthTime":false,"scopeSettings":[{"scope":"openid","defaultScope":false},{"scope":"profile","defaultScope":false},{"scope":"email","defaultScope":false}],"enhanceScopesWithUserPermissions":false,"accessTokenValiditySeconds":7200,"refreshTokenValiditySeconds":14400,"idTokenValiditySeconds":14400,"tlsClientCertificateBoundAccessTokens":false,"forcePKCE":true,"postLogoutRedirectUris":[],"singleSignOut":false,"silentReAuthentication":false,"requireParRequest":false,"backchannelUserCodeParameter":false},"saml":{"entityId":"http://localhost:4200","attributeConsumeServiceUrl":"http://localhost:4200"},"advanced":{"skipConsent":true,"flowsInherited":true}},"identityProviders":[{"identity":"fa0b97f1-7511-4000-8b97-f175116000a5","selectionRule":"","priority":0}],"createdAt":1657545315655,"updatedAt":1657545315797}
get OAuth2 clientId
==> OAuth2 client Id: bc94b5a6-7b66-4440-94b5-a67b66e440ae
==> AM application overview: http://localhost/am/ui/environments/default/domains/local/applications/c9001afd-4632-47e8-801a-fd463247e88f/overview

Securing the Angular application

Next, we’ll use AM to secure our Angular application. Since Gravitee Access Management is OAuth2/OpenID certified, we can use any standard OAuth2 library.

We’ve decided that we will use the following library: https://github.com/manfredsteyer/angular-oauth2-oidc. It is the most popular library that implements the OIDC standard in the Angular world.

yarn add angular-oauth2-oidc

We suggest putting the flow configuration in the environment file. This way, you can have a dedicated setup for each target.

export const environment = {
production: false,
auth: {
clientId: "1e84ec83-4b6b-49a8-84ec-834b6ba9a807",
loginUrl: "http://localhost/am/local/oauth/authorize",
logoutUrl: "http://localhost/am/local/logout",
tokenEndpoint: "http://localhost/am/local/oauth/token",
revocationEndpoint: "http://localhost/am/local/oauth/revoke",
userinfoEndpoint: "http://localhost/am/local/oidc/userinfo",
issuer: "http://localhost/am/local/oidc",
redirectUri: `${window.location.origin}`,
postLogoutRedirectUri: `${window.location.origin}`,
responseType: "code",
scope: "openid profile email",
skipIssuerCheck: false,
requireHttps: false,
},
};

You can find the clientId in the Application overview in the AM Console. For this example, the AM initialization script displays the value to use.

We can’t rely on the auto-discovery option, this is because AM’s well-known URL is http://localhost/am/local/oidc/.well-known/openid-configuration. The library is expecting http://localhost/am/local/.well-known/openid-configuration. AM supports other protocols defining a well-known URL, and prefixing allows us to “split” by protocol. That’s why we need to define “manually” the endpoints ( loginUrl, tokenEndpoint, …)

Next, we will centralize the authorization logic in dedicated service to comply with the Single Responsibility Principle from SOLID:

export class AuthService {
constructor(private readonly oauthService: OAuthService) {}
load() { // (1)
this.oauthService.configure(authorizationCodeFlowConfig);
return this.oauthService.tryLoginCodeFlow({}).then(() => {
this.oauthService.events
.pipe(filter((e) => e.type === "token_received"))
.subscribe((_) => this.oauthService.loadUserProfile());
});
}
login() { // (2)
this.oauthService.initCodeFlow();
}
logout() { // (3)
this.oauthService.logOut();
}
}

(1) We define a method that will be called at the application startup to configure the library to handle the login flow.

(2) We define a method to trigger the login flow.

(3) We could define a method to trigger the logout flow.

Finally, we have to import the module into our AppModule, and configure the library at the startup:

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, OAuthModule.forRoot()],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initApp,
deps: [AuthService],
multi: true,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}
export function initApp(authService: AuthService) {
return () => authService.load();
}

With everything now set up; we can now give it a try 🚀

Login with Gravitee Access Management

Conclusion

Securing a application is done for a number of reasons, including limiting access to data, managing different user experiences, and service monetization.

Using an Identity and Access Management platform allows you to avoid building authentication logic into your core application, reducing additional development effort required for adding new identity providers, as well as focusing effort on the application’s business logic.

We’ve shown you how to secure an Angular application with Gravitee Access Management platform in this blog post. If you want more info on our amazing products, join our community.

You can find all the code used in the blog post in this repository.

--

--