Identity-as-a-Service (IDaaS) : AWS Cognito and ASP.NET Core
This is another article in a series about Identity as a Service. In this article, we are going to see how to configure ASP.NET Core to use AWS Cognito as an identity provider. The concepts are similar to working with other providers, such as Okta, although there are always some subtleties in the configuration.
All code for this example is available on GitHub.
Web App
First, we create a web application in ASP.NET Core in Visual Studio 2017. In this case, the app that gets created by default is enough. Configuring Authentication in ASP.NET Core 2.0 is very simple, most of the changes occur in the startup.cs
file. But before we start setting up the app, we have to have our Identity-as-a-Service. Let’s start with the configuration in AWS Cognito.
Amazon Web Services — New Account
In order to use AWS Cognito, it is necessary to have an Amazon Web Services account. The AWS Cognito service is included in the Free Tier and never expires. We can manage up to 50,000 monthly active users. And it gives us the basics to be able to manage the users of our apps.
To create the account we have to go to the AWS signup. After completing the initial details, we have to fill in more data, including the details of a credit card and automatic verification with a phone call. After creating the account you could follow some indications from Amazon to not use the Root user for the configurations but, in this example, we will ignore it and go directly to AWS Cognito to create the User Pool.
AWS Cognito User Pool
To access AWS Cognito we can look for the option in the All Services menu in the Security, Identity & Compliance section or simply search for it.
The documentation is available in English and other languages such as Spanish. It explains many more details and examples and is highly recommended for those who want to use the service seriously.
The creation starts from the AWS Cognito main page, choosing the option 'Manage Your User Pools.'
Since we have not created any User Pool yet, the only option available is 'Create a User Pool.' First, we have to indicate the name - Pool Name. As the default configuration is useful, we can select the Review Defaults option and create the Pool.
Now we have to configure our app so we can access it. We do this from the General settings > App clients option, choosing Add an app client. We indicate the name of the app - App client name - and that's it.
Then we have to configure how our app will negotiate the identities with AWS Cognito. We configure this from App integration > App client settings.
First of all, we have to select the identity provider - Enabled Identity Providers - in our case. we only have the Cognito User Pool option. Then we have to indicate the URL where AWS Cognito will contact our app after the user completes in sign-in - Callback URL(s). In our case, it is the URL localhost where our app will run, plus the path signin-oidc
. The last step is to configure OAuth 2.0. In our case, we will choose the Authorization code grant and email, openid, and profile values.
Configuration Information
In the General settings > App clients option, we can see the configuration details, mainly the App client id and App client secret that we need to configure the access to our app.
The last value we need is the PoolId that is in General setting to be able to configure the address of where the ASP.NET web app obtains the configuration values. The URL really depends on two variables, the PoolID and the region where our AWS Cognito instance is configured. In this case, US East (Ohio).
https://cognito-idp.us-east-2.amazonaws.com/us-east-2_54E8jWmWw/.well-known/openid-configuration
Another configuration that may be important is the App integration > Domain name. It allows us to configure what will be the domain of the sign-in and sign-up pages.
After the creation of the app in AWS Cognito, we can return to Visual Studio.
Startup.cs Changes
The first changes that are necessary to make are to using
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;
Most of the changes are in the ConfigureServices
method. Some comments before viewing the code.
First, instead of harcoding the properties of the OIDC connection in the code, we use appsettings.json
for more flexibility.
Then, as we are using Razor Pages, the way to indicate which resources to protect is different, using the AuthorizePage
method, in this case only on the Index page.
The third section has two parts, AddAuthentication
to indicate the Authentication andAddOpenIdConnect
schemes to indicate the parameters of the OIDC connection, by far the most important section of the code.
If you read the example using Okta, you will see that AWS Cognito uses different values from the OIDC options, for example, MetadataAddress
instead of Authority
. The rest of the code is almost the same.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<OpenIdConnectOptions>(Configuration.GetSection("Authentication:Cognito"));
var serviceProvider = services.BuildServiceProvider();
var authOptions = serviceProvider.GetService<IOptions<OpenIdConnectOptions>>();
services.AddMvc()
.AddRazorPagesOptions(options => {
options.Conventions.AuthorizePage("/Index");
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ResponseType = authOptions.Value.ResponseType;
options.MetadataAddress = authOptions.Value.MetadataAddress;
options.ClientId = authOptions.Value.ClientId;
options.ClientSecret = authOptions.Value.ClientSecret;
options.SaveTokens = authOptions.Value.SaveTokens;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = authOptions.Value.TokenValidationParameters.ValidateIssuer
};
});
}
And, finally, modify the Configure
method to use the Authentication model that we configured in the previous step.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// The rest of the Configure Method
}
When you run the app, it automatically takes you to the Sign In of AWS Cognito, since to access Index you need the user to be authenticated. Unlike Okta, AWS Cognito allows us to create a new user from this page.
When you run the app, it automatically takes you to the Sign In of AWS Cognito, since to access Index you need the user to be authenticated. Unlike Okta, AWS Cognito allows us to create a new user from this page.
Upon completing the registration, AWS will send us an email with the verification code. After copying it and confirming the account, AWS Cognito returns us to our app, already identified.
Final Words
The configuration of the service in ASP.NET Core 2.0 is very simple and, in a short time, we can be developing our app without worrying about security or managing the users ourselves. This case shows the basic configuration for AWS Cognito but the truth is that any other OIDC service requires a similar amount of effort.
Thanks for reading!
{
"Authentication": {
"Cognito": {
"ClientId": "{clientId}",
"ClientSecret": "{clientSecret}",
"IncludeErrorDetails": true,
"MetadataAddress": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_54E8jWmWw/.well-known/openid-configuration",
"RequireHttpsMetadata": false,
"ResponseType": "code",
"SaveToken": true,
"TokenValidationParameters": {
"ValidateIssuer": true
}
}
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information",
"System": "Information"
}
}
}
All views expressed are my own and do not represent opinions of any entity whatsoever with which I have been, am now, or will be affiliated.