ADFS + Angular + ASP.NET Core API

ADFS + Angular + ASP.NET Core API

Hello everyone!

In this article I’m going to talk about ADFS + Angular + ASP.NET Core API.

I needed to do these 3 things work together and I had a huge difficulty to find content about doing that. When it had information about Angular, it did not have about ASP.NET Core API, or it was about ASP.NET MVC projects. Oh, it was sad!

But now, I have those 3 things working together and I’d like to explain some couple things I learned and also provide a project that may help someone else.

So, let’s get started!

Part 01: ADFS

I’m not going to focus on configuring your application in ADFS, I think you can find enough content about it on internet.

But, you need to know some things:

Because of the frontend will be decoupled of your backend, you need to use an approach of JWT + OAUTH/OIDC, where you’ll have an access token. So basically the “authentication happens” in the frontend and the backend checks if the request is valid, or in other words, if the request has a valid access token in its header.

This is called the Implicit Flow and, based in what I researched in that time, this is just allowed as of ADFS 4.

Besides that, it’s good to have in mind that because it is a SPA application, it there is a specific way to configure it on ADFS, something like “native application” or similar to that. Well, I guess you can find it on Google and after that you’ll have a client ID and other informations.

Part 02: Angular

I won’t focus on creating an Angular application. I imagine you know how to do that. If not, check these links:

https://angular.io/

 https://cli.angular.io/

 I have a project where you can base yourself: https://github.com/gabrielfbarros/aspnetcore-angular-adfs/tree/master/front.

You may imagine that I did not focus in a great structure to the application!

To make the integration with ADFS easier I looked for a lib that could help me with that. I found few ones, but the one that seemed to be more used and worked for me was “angular-oauth2-oidc”. You can check it here: https://www.npmjs.com/package/angular-oauth2-oidc

In my example above, basically I created an Angular 5 application via CLI, and followed what the lib tutorial says. I detail it below:

- You need to add the lib in your package.json file. Look, if you need support for Angular < 6 (4.3 to 5.x) you can download the former version 3.1.4 (npm i angular-oauth2-oidc@^3 --save). If you’re working with Angular 6+, you can use latest version. Check the tutorial in npm website.

- In the app.component, create a constructor and inside it, you configure the OAuthService from the lib:

public constructor(private oauthService: OAuthService) {
  this.oauthService.redirectUri = window.location.origin;
  this.oauthService.clientId = 'YOUR_CLIENT_ID';
  this.oauthService.loginUrl = 'https://YOUR_SERVER/adfs/oauth2/authorize';
  this.oauthService.issuer = 'https://YOUR_SERVER/adfs';
  this.oauthService.scope = "openid profile";
  this.oauthService.responseType = 'id_token token';
  this.oauthService.setStorage(sessionStorage);
  this.oauthService.tryLogin();
}
 
  

You noticed that the redirectUri I just set to the base url of my application. This configuration should be set with the redirect url you configured in ADFS, because after authenticating the user, the ADFS will redirect to that configured url.

The clientId is the one generated by ADFS for your application. It is something like a “guid” value.

The loginUrl is the url to the authorize endpoint of ADFS. Basically you need to change “YOUR_SERVER” by the path to your ADFS server, in the example above.

The issuer is basically the url to the server that holds ADFS, finishing only with “/adfs”, but in my case, this path was different from the previous path loginUrl. I don’t know exactly why. After a lot of tests, I found out that you have an endpoint that provides you several other informations: https://YOUR_SERVER/adfs/.well-known/openid-configuration, and one of the informations in that was the issuer, that I just used in my Angular configuration.

Well that was basically all configuration I needed to the lib “see” my ADFS.

- In the app.module you need to import “OAuthModule.forRoot()” in the imports declaration.

- In my example I created a home.component that has a button that sends a GET request to an API, and the API is guarded by security policies. So, I created my.service, where a created the following function:

getPrivateMessage(): Observable<string> {
  let options = this.getAuthHeader();
  
  return this.http.get(this.UrlService + "home/private", options)
    .map((res: Response) => <string>super.extractData(res))
    .catch(err => super.serviceError(err, true));
}
 
  

Look the “this.getAuthHeader()” call. You can check that my.service extends a base.service, where I created that function:

protected getAuthHeader(): RequestOptions {
  this.Token = this.oauthService.getAccessToken();
  
  let headers = new Headers({ 'Content-Type': 'application/json' });
  headers.append('Authorization', `Bearer ${this.Token}`);
  
  let options = new RequestOptions({ headers: headers });
  return options;
}
 
  

Well, here is where the magic happens! Here I use “this.oauthService.getAccessToken()” to get the access token and I put it in the header as an Authorization Bearer property.

- Besides that, I created an app.routes and an auth-guard.service. In the guard service I check if it there is a valid access token in the session storage. If not or if it fails to log the user in, it will init the implicit flow, that will open the login page of ADFS:

canActivate(routeAc: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
 
  if (!this.oauthService.hasValidAccessToken()) {
    this.oauthService.tryLogin();
  }

  if (!this.oauthService.hasValidAccessToken()) {
    this.oauthService.initImplicitFlow();
    return false;
  }

  return true;
}
 
  

So using this guard in my routes, I guarantee that the user is authenticated:

 { path: ‘home’, component: HomeComponent, canActivate: [AuthGuardService] }

- And last thing but not less important, in my home.component it there is a logout function:

logout(){
 this.oauthService.logOut();
 window.location.href= 'https://YOUR_SERVER/adfs/ls/?wa=wsignoutcleanup1.0';
 }

Well, you can see that it’s very simple, but only after you know all this!

In the home page, after you log in, you’ll see the access token generated by ADFS and that will be sent to the API. You can check it in https://jwt.io/, check what informations is ADFS returning to you. In my case we were able to get several important informations to the application from ADFS.

Part 03: ASP.NET Core API

For the backend, I also have a project where you can base yourself: https://github.com/gabrielfbarros/aspnetcore-angular-adfs/tree/master/back.

You also may imagine that I did not focus in a great structure to the application!

In my example above, basically I created an ASP.NET Core 2.0 API, from the template of Visual Studio.

The main configuration occurs in the class Startup.cs. As I mentioned before, the API will receive requests and for each request it needs to check if a valid access token is provided, otherwise the API should return code 401 (Unauthorized).

To make sure that any request are going to check the access token, you need to add a filter to your MVC pipeline. In the ConfigureServices method:

services.AddMvc(options =>
{
  var policy = new AuthorizationPolicyBuilder()
    .AddAuthenticationSchemes("Bearer")
    .RequireAuthenticatedUser()
    .Build();
    
  options.Filters.Add(new AuthorizeFilter(policy));
});

Look that I defined a name to my authentication scheme: “Bearer”. And now we need to configure the authentication as JWT. Still in the ConfigureServices method:

services.AddAuthentication("Bearer")
  .AddJwtBearer(options =>
  {
    options.Authority = "https://YOUR_SERVER/adfs";
    options.Audience = "microsoft:identityserver:YOUR_CLIENT_ID";
    options.TokenValidationParameters = new TokenValidationParameters()
    {
      ValidIssuer = "https://YOUR_SERVER/adfs/services/trust"
    };
    
    options.Events = new JwtBearerEvents
    {
      OnTokenValidated = async ctx =>
      {
        var claims = new List<Claim>
        {
          new Claim("GivenType", "GivenValue")
        };
        
        ctx.Principal.AddIdentity(new ClaimsIdentity(claims));
      }
    };
  });
 
  

You see that Authority is the issuer of Angular application.

The Audience was my Achilles’ heel! I took some time to found out what information should I use here. You can notice that the value has “microsoft:identityserver:” and the client id. Well, this information I got from the access token, looking it in https://jwt.io/. It is the “aud” value in the token.

The ValidIssuer is the services/trust endpoint of ADFS.

In the Events property, you can configure OnTokenValidated and add Claims to the identity or you can do any other thing that is necessary after the token is validated.

And not mandatory, but very useful is to configure policies, that is a new feature of ASP.NET Core:

services.AddAuthorization(options =>
{
  options.AddPolicy("Given Policy", policy => policy.RequireClaim("GivenType", "GivenValue"));
});

Here we create a policy and says which Claim is necessary to satisfy the policy. Look that the claim is the same I add to the user in the OnTokenValidated above, so the user will have access to that policy.

And to the policy makes sense, we can use it in our Controller:

[Authorize(Policy = “Given Policy”)]

 public class HomeController : Controller

So, with all this configuration, any request, for any controller, it will check against your ADFS if it there is a valid access token in the header. And to this HomeController specifically, it will check if the user complies with the policy we have created.

And that’s it! It doesn’t look so hard now, but I took some time to see some things.

If you have other approaches to this combination of technologies, please let us know, leave your comments!

I hope I could help you!

Bye!

Thank you a lot. Great article!

要查看或添加评论,请登录

Gabriel Faraday的更多文章

  • MongoDB para Desenvolvedores (com exemplo prático de API em .NET Core no final)

    MongoDB para Desenvolvedores (com exemplo prático de API em .NET Core no final)

    Aprenda tudo o que você precisa saber para trabalhar no dia-a-dia com MongoDB como desenvolvedor de software! Há um…

  • GitLab CI/CD with .Net Framework

    GitLab CI/CD with .Net Framework

    Hello everyone! As I promissed, in this article I’m going to talk about CI/CD on GitLab for .Net Framework applications.

  • What is CI/CD? Where can I use it?

    What is CI/CD? Where can I use it?

    Hello everyone! In this article I’m going to talk about CI/CD and about Gitlab, an amazing solution to start (and keep)…

    2 条评论
  • ASP.NET Core API + Google BigQuery

    ASP.NET Core API + Google BigQuery

    Hello everyone! In this article I’m going to talk about how to create an ASP.NET Core API to connect into Google…

    1 条评论

社区洞察

其他会员也浏览了