Introduction
In my last article, Building API Gateway Using Ocelot In ASP.NET Core, I introduced how we can use Ocelot to build our API Gateway with the simplest demo. In this article, I will continue with the topic of Building API Gateway In ASP.NET Core and will show you something about authentication later.
As all we know, API services are protected resources. We should secure them as far as we can!
Normally, we will follow Security to handle the security of our projects. This repository contains the security and authorization middleware for ASP.NET Core. It always makes things easier.
Do we need to make a big change when we import API Gateway into our project? The answer is - No! We only make some imperceptible changes. It means that everything will be easy.
Let's look at the below screenshot first.
This screenshot shows that when we want to access our services, the authentication module of API Gateway should be visited at first. We need to visit the auth service to get the access token at first so that we can visit the protected services with the access_token.
Maybe some of you will put the auth service alone,= which means that the clients should visit auth service to get access_token straight at first, then send a request to API Gateway to do something later. There is no doubt that thing will be all right but I recommend that you put your auth service together with other services.
API Gateway is the entry of all our services; we just make auth service can be accessed by the clients without authentication.
OK, let's begin to do it.
I will use something the same as last article's demo here. I think it may help you to understand more quickly.
Step 1
Create four projects here.
Project Name | Project Type | Description |
APIGateway | ASP.NET Core Empty | entry of this demo |
CustomersAPIServices | ASP.NET Core Web API | API Service that handles something about customers |
AuthServer | ASP.NET Core Web API | API Service that handles something about auth |
ClientApp | Console App | This is a Console App that simulates client. |
Make APIGateway and CustomerAPIServices project the same as in the last demo. You can find them in APIGatewayBasicDemo.
Step 2
Finish AuthServer. This service is mainly for generating access_token for the request. What we need to do is add a method to handle this.
- [HttpGet]
- public IActionResult Get(string name, string pwd)
- {
-
- if (name == "catcher" && pwd == "123")
- {
- var now = DateTime.UtcNow;
-
- var claims = new Claim[]
- {
- new Claim(JwtRegisteredClaimNames.Sub, name),
- new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
- new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
- };
-
- var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_settings.Value.Secret));
- var tokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = signingKey,
- ValidateIssuer = true,
- ValidIssuer = _settings.Value.Iss,
- ValidateAudience = true,
- ValidAudience = _settings.Value.Aud,
- ValidateLifetime = true,
- ClockSkew = TimeSpan.Zero,
- RequireExpirationTime = true,
-
- };
-
- var jwt = new JwtSecurityToken(
- issuer: _settings.Value.Iss,
- audience: _settings.Value.Aud,
- claims: claims,
- notBefore: now,
- expires: now.Add(TimeSpan.FromMinutes(2)),
- signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
- );
- var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
- var responseJson = new
- {
- access_token = encodedJwt,
- expires_in = (int)TimeSpan.FromMinutes(2).TotalSeconds
- };
-
- return Json(responseJson);
- }
- else
- {
- return Json("");
- }
- }
When verifying the users, I just use the hard code here because it's not an important thing to implement it in this article's topic!
We have finished auth service. We can run it up now.
Step 3
Turn to CustomerAPIServices project, we should secure this service!!
Edit the Startup class so that we can enable the authentication. I use JwtBearer here and I set the default authentication scheme to TestKey which I will mention in the next step.
- public void ConfigureServices(IServiceCollection services)
- {
- var audienceConfig = Configuration.GetSection("Audience");
-
- var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
- var tokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = signingKey,
- ValidateIssuer = true,
- ValidIssuer = audienceConfig["Iss"],
- ValidateAudience = true,
- ValidAudience = audienceConfig["Aud"],
- ValidateLifetime = true,
- ClockSkew = TimeSpan.Zero,
- RequireExpirationTime = true,
- };
-
- services.AddAuthentication()
- .AddJwtBearer("TestKey", x =>
- {
- x.RequireHttpsMetadata = false;
- x.TokenValidationParameters = tokenValidationParameters;
- });
-
- services.AddMvc();
- }
- public void Configure(IApplicationBuilder app)
- {
- app.UseAuthentication();
- app.UseMvc();
- }
And then, we should use Authorize attribute in our method.
- [Authorize]
- [HttpGet]
- public IEnumerable<string> Get()
- {
- return new string[] { "Catcher Wong", "James Li" };
- }
Here, I just authorize of them because we need to show the not protected resources for comparison.
Note
This project is based on ASP.NET Core 2.0. If you are working with 1.x, you may find some difference here. You can follow Migrating Authentication and Identity to ASP.NET Core 2.0 to migrate.
Run it up too!
Step 4
The most important step is now arriving. Configure the authentication in your API Gateway.
- public void ConfigureServices(IServiceCollection services)
- {
- var audienceConfig = Configuration.GetSection("Audience");
-
- var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(audienceConfig["Secret"]));
- var tokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuerSigningKey = true,
- IssuerSigningKey = signingKey,
- ValidateIssuer = true,
- ValidIssuer = audienceConfig["Iss"],
- ValidateAudience = true,
- ValidAudience = audienceConfig["Aud"],
- ValidateLifetime = true,
- ClockSkew = TimeSpan.Zero,
- RequireExpirationTime = true,
- };
-
- services.AddAuthentication()
- .AddJwtBearer("TestKey", x =>
- {
- x.RequireHttpsMetadata = false;
- x.TokenValidationParameters = tokenValidationParameters;
- });
-
- services.AddOcelot(Configuration);
- }
You will find that most of the code is the same as Customer Service.
When Ocelot runs, it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey and check that there is an Authentication Provider registered with the given key. If there isn't, then Ocelot will not start up. If there is, then the ReRoute will use that provider when it executes.
So, we also should modify the configuration.json file. We should add a new node named AuthenticationOptions and make the AuthenticationProviderKey the same as what we defined in the Startup class.
- {
- "DownstreamPathTemplate": "/api/customers",
- "DownstreamScheme": "http",
- "DownstreamHost": "localhost",
- "DownstreamPort": 9001,
- "UpstreamPathTemplate": "/customers",
- "UpstreamHttpMethod": [ "Get" ],
- "AuthenticationOptions": {
- "AuthenticationProviderKey": "TestKey",
- "AllowedScopes": []
- }
- }
We also run it up now.
Note
If you meet the following error when you run this project, you need to check your code whether the AddJwtBearer method specifies the authentication scheme.
Unhandled Exception: System.InvalidOperationException: Scheme already exists: Bearer
Step 5
We have done all preparation work here. Now, we should use our client app to simulate some request of the API Gateway.
First of all, we need to add a method to get the assess_token.
- private static string GetJwt()
- {
- HttpClient client = new HttpClient();
- client.BaseAddress = new Uri( "http://localhost:9000");
- client.DefaultRequestHeaders.Clear();
- var res2 = client.GetAsync("/api/auth?name=catcher&pwd=123").Result;
- dynamic jwt = JsonConvert.DeserializeObject(res2.Content.ReadAsStringAsync().Result);
- return jwt.access_token;
- }
Then, write some code to visit the customer services via API Gateway.
- static void Main(string[] args)
- {
- HttpClient client = new HttpClient();
-
- client.DefaultRequestHeaders.Clear();
- client.BaseAddress = new Uri("http://localhost:9000");
-
-
-
- var resWithoutToken = client.GetAsync("/customers").Result;
-
-
-
-
-
- client.DefaultRequestHeaders.Clear();
- var jwt = GetJwt();
-
- client.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
- var resWithToken = client.GetAsync("/customers").Result;
-
-
-
-
- client.DefaultRequestHeaders.Clear();
- var res = client.GetAsync("/customers/1").Result;
-
-
-
- Console.Read();
- }
When you run this project, you may find the results as follows.
So far, we have done the job.
Here is the source code you can find on my GitHub page.
Summary
This article introduced how to configure authentication when you use Ocelot as your API Gateway. Hope this will help you.