Implicit OAuth Workflow

The DS Server Angular components implement an integrated, implicit OAuth security handling. In case, the client ID and client secret is passed as a property to the view, the component takes care of the complete OAuth workflow to request a valid access token.

<tx-ds-document-editor
width="1024px"
height="1024px"
serviceURL="https://trial.dsserver.io"
oauthClientID="dsserver.u5NQQHkgjmCRAOeChUVc19zNFJ9aivKz"
oauthClientSecret="tPGgkutg8oYuSHPbfuRfE5DMf9arUCEg"
</tx-ds-document-editor>
view raw test.html hosted with ❤ by GitHub

The following diagram shows the client credentials workflow that connects the client DocumentEditor directly with DS Server by sending the client credentials to DS Server to retrieve a valid access token:

Security workflow

Using Access Tokens

The disadvantage of the above method is that the client credentials are exposed client-side. To avoid that, the components can be authorized directly with an access token:

<tx-ds-document-editor
width="500px"
height="500px"
serviceURL="https://trial.dsserver.io"
accessToken="Ghgf6376722GGJHFFJGHDOOIGD56657665">
</tx-ds-document-editor>
view raw gistfile1.html hosted with ❤ by GitHub

The next diagram shows the authorization with an access token that has been acquired by a server application in between:

Security workflow

In order to retrieve the access token from the DS Server Web API endpoints, a server-side process is required. In this case, the credentials are securely stored on the server and the client will only see the used access token.

Web API

To demonstrate this process, an ASP.NET Core web application is used to provide a Web API that requests the OAuth access tokens from DS Server in order to return a valid access token. This endpoint is then called by Angular before the view component is rendered.

The following code shows the implementation of the Web API method AccessToken:

[HttpGet]
[Route("AccessToken")]
public async System.Threading.Tasks.Task<ActionResult> AccessTokenAsync() {
// security credentials
string clientId = "";
string clientSecret = "";
string serviceUrl = "https://trial.dsserver.io";
string ClientCredentials = "client_credentials";
AccessTokenResponse token;
HttpClient m_client = new HttpClient();
// generate the payload
var payload = new Dictionary<string, string> {
["grant_type"] = ClientCredentials,
};
// token endpoint
string requestUri = $"{serviceUrl}/oauth/token";
// create the request message
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, requestUri) {
Content = new StringContent(
UrlEncode(payload),
Encoding.UTF8,
"application/x-www-form-urlencoded")
};
// Add basic auth header containing client id and secret
string credentials = $"{clientId}:{clientSecret}";
byte[] credentialsUtf8 = Encoding.UTF8.GetBytes(credentials);
string credentialsB64 = Convert.ToBase64String(credentialsUtf8);
tokenRequest.Headers.Authorization =
new AuthenticationHeaderValue("Basic", credentialsB64);
// send the request
var tokenResponse = await m_client.SendAsync(tokenRequest);
// retrieve and return the token
var tokenResponseStream = await tokenResponse.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<AccessTokenResponse>(tokenResponseStream);
return Ok(token);
}
public string UrlEncode(Dictionary<string, string> dict) {
return string.Join("&", dict.Keys.Select(k => $"{k}={WebUtility.UrlEncode(dict[k])}"));
}
view raw test.cs hosted with ❤ by GitHub

Injectable Service

In the Angular application, a service is implemented to call the Web API and to return the access token:

import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class OauthService implements Resolve<any> {
public _http: HttpClient;
public _baseUrl: string;
constructor(http: HttpClient, @Inject('BASE_URL') baseUrl: string) {
this._http = http;
this._baseUrl = baseUrl;
}
resolve() {
return this._http.get<any>(this._baseUrl + 'oauth/accesstoken');
}
}
view raw test.ts hosted with ❤ by GitHub

The service implements a resolve() method that is invoked when the navigation starts. The router waits for the data to be resolved before the route is finally activated. This must be defined in the imports section of the app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { DocumentEditorModule } from '@txtextcontrol/tx-ng-ds-document-editor';
import { OauthService } from './oauth.service';
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
FormsModule,
RouterModule.forRoot([
{
path: '',
component: HomeComponent,
pathMatch: 'full',
resolve: { token: OauthService } },
]),
DocumentEditorModule
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule { }
view raw test.ts hosted with ❤ by GitHub

In the home.component.ts, the result of this attached service is passed to the property _accessToken:

import { ApplicationInitStatus, Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-home',
templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
_accessToken: string = "";
constructor(private _routes: ActivatedRoute) { }
ngOnInit(): void {
this._routes.data.subscribe((response: any) => {
this._accessToken = response.token.accessToken;
})
}
}
view raw home.ts hosted with ❤ by GitHub

This _accessToken is then used directly in the home.component.html:

<tx-ds-document-editor
width="500px"
height="500px"
serviceURL="https://trial.dsserver.io"
accessToken="{{ _accessToken }}">
</tx-ds-document-editor>
view raw test.html hosted with ❤ by GitHub

Is this Safe?

The positive of the above method is that the client credentials are not exposed client-side. But the security problem is now shifted to the server as the Web API is exposed and could be accessed by other requests. In order to avoid that, the Web API should be secured. For example by using one of the following concepts:

  • CORS: Enable only specific hosts.
  • Security Middleware: Filter requests (used in this demo).
  • Another Authorization: Use second authorization (login) for the Web API.

Security workflow

You can test this method by downloading the sample from our GitHub repository. Let us know, if you have any questions.