Atilla Tanrikulu

I am an experienced software engineer and architect living in Germany. I’m passionate about distributed scalable enterprise web-based microservices/applications and delivering great user experiences. I have created some amazing enterprise-level applications that many people have used and hopefully enjoyed.

Articles

Java Quick Reference Apache Kafka Tutorial Guvenli Kod Gelistirme Making an Enterprise Scale Angular Project Step by Step Nightly SQL Server Database Backup with command line batch file and windows scheduler AOP Framework without proxy pattern IdentityServer Nedir Middleware Pattern With Csharp And Javascript Docker most used commands Online Proje Dokumantasyonu, Docker, Nginx, mdwiki How to use Github Pages for static websites Inheritance with JavaScript, EC6 (ECMAScript 6, ECMAScript 2015) Object oriented javascript and Inheritance Singleton Pattern with Javascript Factory Pattern with Javascript Open terminal here mac os x service IdentityServer4-Angular-6-integration JMater notlari, kurulum ve kullanim Learn Jekyll in 12 Steps Make Mac Application with Automater from sh script Make spotlight index markdown or code files OAuth 2.0 Nedir (RFC6749) Using Custom CSS and Custom JavaScript to an Angular Project Cross Platform Desktop Application With .Net Core 2x and Angular 6x front-end projects with nodejs gulp bower yeoman and angularjs Host Asp.Net Core on Linux with Apache Redis kurulumu ve ayarlari Useful Mac OS Apps Choosing internet connection on multiple interface windows Name Server Kurulumu How to define domain name for your dynamic IP SQL table data compare, and prepare insert satements Useful Git Commands TFS ile Otomatik deployment yapmak Spring Boot Tutorial Sql server icin maliyetli sorgularin tespit edilmesi Arama Motoru Optimizasyonu (SEO) My installed mac apps

IdentityServer4-Angular-6-integration

1. Configure IdentityServer4

In this example we are using RESTFul API, it’s name is core.api

// File name = Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
    // configure identity server with in-memory stores, keys, clients and resources
    services.AddIdentityServer(options =>
        {
            options.Events.RaiseErrorEvents = true;
            options.Events.RaiseInformationEvents = true;
            options.Events.RaiseFailureEvents = true;
            options.Events.RaiseSuccessEvents = true;
        })
        .AddDeveloperSigningCredential()
        .AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients())
        .AddTestUsers(Config.GetUsers());

    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }
    app.UseCors(policy => policy.AllowAnyOrigin()
                                .AllowAnyHeader()
                                .AllowAnyMethod());

    
    app.UseStaticFiles();

    app.UseIdentityServer();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}
//File name = Config.cs

// Identity Resources
public static IEnumerable<IdentityResource> GetIdentityResources(){
  return new List<IdentityResource>
  {
      new IdentityResources.OpenId(),
      new IdentityResources.Profile(),
      new IdentityResources.Email(),
      new IdentityResource {
          Name = "role",
          DisplayName="Your role names",
          Description="Your role names and role codes", 
          ShowInDiscoveryDocument=true
      }
  };
}

// authorized applications, protedted resources
public static IEnumerable<ApiResource> GetApiResources()
{
  return new List<ApiResource> { 
      new ApiResource("core.api", "Core.API", new List<string> { "name", "role", "email" }), 
      new ApiResource("alarm.api", "Alarm.API") 
  };
}


// Angular client 
public static IEnumerable<Client> GetClients() =>
new Client
{
    ClientId = "cweb",
    ClientName = "cweb",
    AccessTokenLifetime = 60*60,// 60 minutes
    AllowedGrantTypes =  GrantTypes.Implicit,
    AlwaysSendClientClaims=true,
    AlwaysIncludeUserClaimsInIdToken = true,
    ClientSecrets ={new Secret("*****".Sha256())},
    RequireConsent = false,
    AllowAccessTokensViaBrowser = true,
    AllowOfflineAccess = true,
    AccessTokenType = AccessTokenType.Jwt,
    AllowedScopes = { "openid", "profile", "email", "role", "core.api" },
    RedirectUris = { "http://localhost:4200" },
    PostLogoutRedirectUris = { "http://localhost:4200" },
    AllowedCorsOrigins = new List<string>{
        "http://127.0.0.1:4200", // web
        "http://127.0.0.1:5001", // api
        "http://localhost:4200",
        "http://localhost:5001",
    }
}

// Users
public static List<TestUser> GetUsers()
{
    return new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "systemuser",
            Password = "123",
            Claims = {
                new Claim(JwtClaimTypes.Name,"Systemuser"),
                new Claim(JwtClaimTypes.Role,"system"),
                new Claim(JwtClaimTypes.Email, "systemuser@mycompany.com")
            }
        },
        new TestUser
        {
            SubjectId = "2",
            Username = "adminuser",
            Password = "123",
            Claims = {
                new Claim(JwtClaimTypes.Name,"Adminuser"),
                new Claim(JwtClaimTypes.Role,"admin"),
                new Claim(JwtClaimTypes.Email, "adminuser@mycompany.com")                    }
        },
        new TestUser
        {
            SubjectId = "3",
            Username = "testuser",
            Password = "123",
            Claims = {
                new Claim(JwtClaimTypes.Name,"Testuser"),
                new Claim(JwtClaimTypes.Role,"test"),
                new Claim(JwtClaimTypes.Email, "testuser@mycompany.com")
            }
        }

    };
}

you can download full source code from : https://github.com/atillatan/sso-with-identityserver4

2. Configuration Angular Project

npm install angular-auth-oidc-client --save

Then add ConfigService to Angular project

ng generate service config

then paste below code

// File Name = config.service.ts
import { Injectable, EventEmitter, Output, Injector } from '@angular/core';
import { AuthModule, OidcSecurityService, OpenIDImplicitFlowConfiguration, AuthWellKnownEndpoints } from 'angular-auth-oidc-client';
import { MessageService } from './message.service';

@Injectable({
  providedIn: 'root'
})
export class ConfigService {

  // tslint:disable-next-line:no-output-on-prefix
  @Output() onConfigurationLoaded: EventEmitter<boolean> = new EventEmitter<boolean>();

  config: any;
  wellKnownEndpoints: any;

  constructor() { }

  async loadConfig(configUrl: string) {
    try {
      const response = await fetch(configUrl);

      if (!response.ok) {
        throw new Error(response.statusText);
      }

      this.config = await response.json();
      await this.loadSSOConfig(this.config.SSOAddress);
    } catch (err) {
      console.error(`ConfigService 'loadConfig' threw an error on calling ${configUrl}`, err);
      this.onConfigurationLoaded.emit(false);
    }
  }

  async loadSSOConfig(stsServer: string) {
    try {
      const response = await fetch(`${stsServer}/.well-known/openid-configuration`);

      if (!response.ok) {
        throw new Error(response.statusText);
      }

      this.wellKnownEndpoints = await response.json();
      this.onConfigurationLoaded.emit(true);
    } catch (err) {
      console.error(`ConfigService 'loadSSOConfig' threw an error on calling ${stsServer}`, err);
      this.onConfigurationLoaded.emit(false);
    }
  }

  setupSSO(oidcSecurityService: OidcSecurityService) {
    const c = new OpenIDImplicitFlowConfiguration();
    c.stsServer = this.config.SSOAddress;
    c.redirect_url = window.location.origin;
    c.client_id = this.config.SSOClientId;
    c.response_type = 'id_token token';
    c.scope = 'openid profile email role core.api';
    c.post_logout_redirect_uri = window.location.origin + '/unauthorized';
    c.forbidden_route = '/forbidden';
    c.unauthorized_route = '/unauthorized';
    c.auto_userinfo = true;
    c.log_console_warning_active = true;
    c.log_console_debug_active = true;
    c.max_id_token_iat_offset_allowed_in_seconds = 10;
    c.start_checksession = false;
    c.silent_renew = false;
    const wn = new AuthWellKnownEndpoints();
    wn.setWellKnownEndpoints(this.wellKnownEndpoints);
    oidcSecurityService.setupModule(c, wn);
  }

}

Then configure your app.module.ts

// app.module.ts
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { HttpClientModule } from '@angular/common/http'; 
import { AuthModule, OidcSecurityService, OpenIDImplicitFlowConfiguration, AuthWellKnownEndpoints } from 'angular-auth-oidc-client';
import { ConfigService } from './services/config.service';
 
export function loadConfig(configService: ConfigService) {
  console.log('APP_INITIALIZER STARTING');
  return () => configService.loadConfig(`http://localhost:5001/api/js/json/config.js`);
}
 
@NgModule({
    imports: [
        ...
        HttpClientModule,
        AuthModule.forRoot()
    ],
    declarations: [
        ...
    ],
    providers: [
        OidcConfigService,
        {
          provide: APP_INITIALIZER,
          useFactory: loadConfig,
          multi: true,
          deps: [ConfigService]
        },
        ConfigService,
        ...
    ],
    bootstrap:    [AppComponent],
})

export class AppModule {

  constructor(
    private oidcSecurityService: OidcSecurityService,
    private configService: ConfigService,
  ) {
    registerLocaleData(localeTr, 'tr');

    this.configService.onConfigurationLoaded.subscribe(() => this.configService.setupSSO(this.oidcSecurityService));
    console.log('APP STARTING');
  }
}

Then provide API method on your RESTFul service, it must return like below

//  http://localhost:5001/api/js/json/config.js
{
  Name: "core", 
  DefaultLanguage: "tr-TR",
  DefaultPagingSize: "15",  
  DefaultAPIAddress: "http://localhost:5001",
  SSOAddress: "http://localhost:5000",
  SSOClientId: "cweb",
  AllowedMaxExportSize: "2000",
  FileUploadPath: "wwwroot/files",
}

Then generate unauthorized component

ng generate component `unauthorized`

Then pase below code to unauthorized.component.html

<br>

<div class="alert alert-danger">
  <strong>\\</strong>
</div>

Then pase below code to unauthorized.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-unauthorized',
  templateUrl: './unauthorized.component.html'
})
export class UnauthorizedComponent implements OnInit {

  public message: string;
  public values: any[] = [];

  constructor() {
      this.message = '401: You have no rights to access this. Please Login';
  }

  ngOnInit() {
  }

}

Then generate navmenu component

ng generate component navmenu

Then pase below code to navmenu.component.html

<!--#region NAVBAR -->
<nav class="navbar navbar-expand-lg  navbar-dark bg-dark">
  <a class="navbar-brand" href="#">Demo App</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" (click)="isCollapsed = !isCollapsed" data-target="#navbarText"
    aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="navbarText" [ngbCollapse]="isCollapsed">
    <ul class="navbar-nav mr-auto">
      <li class="nav-item" [routerLinkActive]="['active']">
        <a class="nav-link" routerLink="/home">Home</a>
      </li>
      <li class="nav-item" [routerLinkActive]="['active']">
        <a class="nav-link" routerLink="/dashboard">Dashboard</a>
      </li>
      <li class="nav-item" [routerLinkActive]="['active']"><a class="nav-link" *ngIf="!isAuthorized" (click)="login()"><span class="glyphicon glyphicon-user"></span>LOGIN</a></li>
      <li class="nav-item" [routerLinkActive]="['active']"><a class="nav-link" *ngIf="isAuthorized" (click)="logout()"><span class='glyphicon glyphicon-log-out'></span>LOGOUT</a></li>
    </ul>
  </div>
</nav>
<!--#endregion NAVBAR -->

Then pase below code to navmenu.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { OidcSecurityService } from 'angular-auth-oidc-client';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-navmenu',
  templateUrl: './navmenu.component.html'
})
export class NavmenuComponent implements OnInit, OnDestroy {

  isCollapsed: Boolean = true;
  isAuthorizedSubscription: Subscription;
  isAuthorized: Boolean = false;


  constructor(
    public oidcSecurityService: OidcSecurityService
  ) {

    if (this.oidcSecurityService.moduleSetup) {
      this.doCallbackLogicIfRequired();
    } else {
      this.oidcSecurityService.onModuleSetup.subscribe(() => {
        this.doCallbackLogicIfRequired();
      });
    }
  }


  ngOnInit(): void {
    this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe(
      (isAuthorized: boolean) => {
        this.isAuthorized = isAuthorized;
      });
  }

  private doCallbackLogicIfRequired() {
    if (window.location.hash) {
      this.oidcSecurityService.authorizedCallback();
    }
  }

  ngOnDestroy(): void {
    this.oidcSecurityService.onModuleSetup.unsubscribe();
  }

  login() {
    this.oidcSecurityService.authorize();
  }

  logout() {
    this.oidcSecurityService.logoff();
  }

}

Adding AuthorizationGuard create authorization.guard.ts file ad paste below code

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { OidcSecurityService } from 'angular-auth-oidc-client';

@Injectable()
export class AuthorizationGuard implements CanActivate {

  constructor(
    private router: Router,
    private oidcSecurityService: OidcSecurityService
  ) { }

  public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
    console.log(route + '' + state);
    console.log('AuthorizationGuard, canActivate');

    return this.oidcSecurityService.getIsAuthorized().pipe(
      map((isAuthorized: boolean) => {
        console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized);

        if (isAuthorized) {
          return true;
        }

        this.router.navigate(['/unauthorized']);
        return false;
      })
    );
  }
}

Add AuthorizationGuard to providers in app.module.js

....
import { AuthorizationGuard } from './services/authorization.guard';
...

providers: [
    OidcSecurityService,
    AuthorizationGuard,
    ...
  ],

Then add your routin config

const routes: Routes = [
   ...
  { path: 'home', component: HomeComponent, canActivate: [AuthorizationGuard]  },
  { path: 'dashboard', component: DashboardComponent, canActivate: [AuthorizationGuard] },
  { path: 'detail/:id', component: UserDetailComponent, canActivate: [AuthorizationGuard] },
  { path: 'users', component: UsersComponent, canActivate: [AuthorizationGuard] },
  { path: 'unauthorized', component: UnauthorizedComponent}
];

Http Intercepter

The HttpClient allows you to write interceptors. We would be to intercept any outgoing HTTP request and add an authorization header. Keep in mind that injecting OidcSecurityService into the interceptor via the constructor results in a cyclic dependency. To avoid this use the injector instead.

/// auth.Interceptor.ts
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    private oidcSecurityService: OidcSecurityService;
 
    constructor(private injector: Injector) {
    }
 
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        let requestToForward = req;
 
        if (this.oidcSecurityService === undefined) {
            this.oidcSecurityService = this.injector.get(OidcSecurityService);
        }
        if (this.oidcSecurityService !== undefined) {
            let token = this.oidcSecurityService.getToken();
            if (token !== "") {
                let tokenValue = "Bearer " + token;
                requestToForward = req.clone({ setHeaders: { "Authorization": tokenValue } });
            }
        } else {
            console.debug("OidcSecurityService undefined: NO auth header!");
        }
 
        return next.handle(requestToForward);
    }
}

Then add to app.module.ts following code

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
...
import { AuthInterceptor } from './services/auth.Interceptor';
...
providers: [

    .....
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },
    ....
   
  ],

3. API configuration

Add library to your project.csproj

// project.csproj
  <ItemGroup>
    <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.4.0" />    
  </ItemGroup>

Then add below code to startup.cs

public void ConfigureServices(IServiceCollection services){

    services.AddCors();

    services.AddMvc()

    // ...
    services.AddAuthorization()
    .AddAuthentication("Bearer")
    .AddIdentityServerAuthentication(options =>
    {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;
        options.ApiName = "core.service.api";
        options.ApiSecret = "*****";
    });
    // ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env){
// ...

app.UseAuthentication();

// ...
}
Date: 2018-05-25 10:20:00 +0000