Serverless Framework with server-side authentication and Cognito - rest

I have already implemented various REST-APIs using the Serverless Framework with APIG, DynamoDB as data storage and Cognito for user authentication with Angular2 as frontend. The functions can easily be secured by a Cognito authorizer on the server-side. The downside is that I have to integrate AWS SDK in my frontend applications in order to authenticate the user with Cognito first (signup/signin, ...). I could also use the AWS_IAM authorizer but then I also have to sign all requests on the client side with an AWS specific signature before sending the request to the API Gateway.
Now I was wondering if there is any possibility to keep authentication and authorization on the server side, so I can use an open standard like JSON Web Tokens for signup/signin? This would allow me to open my REST-API for other developers as well without forcing them to use Cognito at all.
I know that one possibility would be to implement a custom authorizer for my lambda functions but isn't there anything stable, which can be used "out-of-the-box" already? Most of the examples I found are using Cognito or IAM auth AWS signature signin on client side (e.g. serverless-stack.com).
It is curious that I didn't find any useful informations about this on the web so far, since I think that this is a typical use case for REST APIs. Or do I have a conceptual misunderstanding about API Gateway + Cognito?

I have been through the same trouble in understanding the way how AWS Cognito works and what options are available to implement authentication & authorization. Unfortunately there is no out-of-the-box method available to do it for your requirement. Nevertheless let's hope that Amazon comes up with a feature very soon.
Basically, there are 3 options available to implement authentication.
AWS_IAM
Cognito Authorizer
Custom Authorizer
AWS_IAM
In addition to authentication, this method can be used to implement authorization using IAM Roles or IAM Users easily. The only downside of it is that you need to send a request signed with an aws-signature-4 which is not the standard way that we have seen in IDP services like Auth0.
Cognito Authorizer
This method meets the expectation of sending a JWT token with API requests. You can create users in Cognito User Pool and then use it to authenticate and generate an IdToken. However, this method will only allow you to authenticate users; authorization needs to be handled in method level.
Custom Authorizer
This method can be used to write your own way of authentication and authorization. Also it helps to eliminate writing authorization logic in API methods. The ideal solution would be to use AWS Cognito User Pool to authenticate users and then generate a policy document for IAM Role to access resources.
Here is an example AWS cognito userpools JavaScript SDK get user's policy documents.
Also keep in mind that this solution will be invoking an extra lambda function for each request that you make.

You can use Cognito Auth to Server-side. Following would be the steps.
Implementing Sign-up and Sign-in
Implement Sign-up form in the frontend and API Gateway endpoint(e.g /register) using Lambda to receive, the Sign-up data, which will create user in Cognito using AWS SDK. For detailed reference check this link.
AWSCognito.config.region = 'us-east-1'; //This is required to derive the endpoint
var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var attributeList = [];
var dataEmail = {
Name : 'email',
Value : 'email#mydomain.com'
};
var dataPhoneNumber = {
Name : 'phone_number',
Value : '+15555555555'
};
var attributeEmail = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataEmail);
var attributePhoneNumber = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataPhoneNumber);
attributeList.push(attributeEmail);
attributeList.push(attributePhoneNumber);
userPool.signUp('username', 'password', attributeList, null, function(err, result){
if (err) {
alert(err);
return;
}
cognitoUser = result.user;
console.log('user name is ' + cognitoUser.getUsername());
});
Do similarly for the Sign-in by creating an frontend & API Gateway endpoint(e.g /login)
var authenticationData = {
Username : 'username',
Password : 'password'
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
/* Use the idToken for Logins Map when Federating User Pools with Cognito Identity or when passing through an Authorization Header to an API Gateway Authorizer */
console.log('idToken + ' + result.idToken.jwtToken);
},
onFailure: function(err) {
alert(err);
},
});
Storing and sending the JWT from your Browser and Validating at API Gateway.
After receiving the JWT from the Sign-in API endpoint, you can store it locally in user's browser, using HTML5 Localstorage, Sessionstorage or Client Side Cookie. Optionally if you need to use Server Side Cookies, it will require to have a Proxy backend which keeps the Session state with the Web App and Convert it to the JWT to invoke the API Gateway.
From the Web Browser(Assuming your client directly invokes API Gateway) set a HTTP header called Authorization and forward the JWT to the API Gateway invocations.
At API Gateway use Cognito Authorizer to as the Authorize the token where it will also forward the user identity resolved to your Lambdas.
Note: Here I have purposefully avoided the IAM Authorization since it will require some additional work from Web App JavaScripts to implement Signature 4 Signing at Browser and also requires to refresh the token frequently which is straightforward with AWS JavaScript SDKs but will become complex if you need to implement it on your own.

Please take a look at this here.
The example demonstrates various configurations that include custom authorizers, cognito, lambda, dynamoDB etc.

Related

Approah on creating clients/realms for separate service (frontend and backend)

I'm new to keycloak and would like to check what is the common design on the said architecture.
I have 1 backend(quarkus) 1 frontend (angular) and 1 flutter.
I would like to see if I could leverage the features of client. My idea is to have a separate client within the realm. For example
REALM = MyAppRealm
Client = backend-client and front-endclient
Is it possible that the token i got from front-endclient can be use to access the api from the backend?
Reason I have this setup is that the front-endclient has a public accesstype while the backend has confidential
to sum up. I would like to see if i can reuse the token i got from front-endclient to my backend-client
Yes of course you can do that.
The purpose of openid is to share authentication and authorization between a diversity of clients without needing to share credentials (no password is known by any of the clients). A trusted third party (here Keycloak) will give back a signed token in exchange for credentials. And this token will be a proof of who the user is and what he is allowed to do in the communications between the frontend and backend.
To sum up :
Your angular frontend authenticates an user using a public client and an implicit flow. When successfully authenticated, the frontend obtains an access token and a refresh token.
When making a REST call to the backend, your frontend needs to set the header Authorization using the access token as a bearer token ('Authorization: Bearer insert access token here'). You can automate this by using an interceptor (example)
Finally, when your backend receive an API request it can check the Authorization header to ensure the request is authenticated and authorized. For how to do that with Quarkus, everything is explained in this documentation page : https://quarkus.io/guides/security-openid-connect

Next Auth + Next JS

I am trying to use NextAuth as auth provider for my project. I have a requirement where I have Credential based login/password. In this case when I login I have to pass the username/password to the custom API (for ex.: abc.com/auth/login). This API as success will return me a JWT for future communication to access their resources.
What I understood from NextAuth that it maintain its own session and JWT(if DB not provided). This feature works in my case but I have to maintain the JWT which the API has returned me(mentioned above). So now there are two JWT one which I received from API and the one which NextAuth has created.
My question:
Is there a way which I can use to maintain the custom JWT which I received from API?
Is there a way if API token has been expired to tempered so I can kill NextAuth session.
What is the best way to keep NextAuth Session and Custom JWT token in sync?
Thanks in advance!
Got the answer on Next-auth repo discussions itself.
This solution worked for me.
So, we can let next-auth generate the JWT token which contains the same payload as the one provided by the API (We can disable the token signature verification in the API).
Then update the next-auth configuration to have a save token in cookie has httpOnly: false so we can access the token server and client sides by adding it in the configuration:
const options = {
// ...
cookies: { sessionToken: { name: `next-auth.session-token`, options: { httpOnly: false } } },
}
After that we can use the code to get the JWT token to be passed to the API calls from the server and client sides:
import cookies from 'next-cookies'
import Cookies from 'js-cookie'
// Server-Side
cookies(context)['next-auth.session-token']
// Client-side
Cookies.get('next-auth.session-token')
Now we just need to figured out how to save my JWT token provided by my API instead of using the one generated by next-auth.
Then we will be able to reactivate the signature verification in the backend API.
You can follow the thread here

Public REST API Authentication and Authorization using AWS Cognito

I'm developing a REST API that will be accessed by customers, and I'd like to use AWS Cognito to handle authentication and authorization.
However, I want to abstract AWS Cognito from my customers, so that if I change from AWS Cognito to a different service, they don't have to change their code (just the API Key).
Unfortunately, after many days reading AWS's documentation, I couldn't find an answer to the following:
How can I give my clients a "personal access token" that never expires, that they can use to authenticate their scripts and back-end services, similar to what you can use for authenticating with GitHub?
So far, I created a REST Endpoint where clients can POST their username and password and I talk to AWS Cognito on their behalf, to do the authentication:
var request = new AdminInitiateAuthRequest
{
UserPoolId = "MyUserPoolId",
ClientId = "MyUserPoolAppClientId",
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
};
request.AuthParameters.Add("USERNAME", "myuser#mydomain.com");
request.AuthParameters.Add("PASSWORD", "123456"); // Relax, this is demo code
string accessToken = string.Empty;
var response = await _identityProvider.AdminInitiateAuthAsync(request);
var authResult = response.AuthenticationResult;
This all works, and authResult gives me 3 tokens that (I think) expire after 30 days (which I can configure up to 3650 days maximum).
This gives me 3 tokens (Access, ID, Refresh). I feel like giving these 3 tokens to the caller would be a bad choice as it's Cognito-specific...
How can I give my customers an API Key, that I can then use to authenticate them with Cognito?

Programatically create an OpenID Connect id_token with IdentityServer3

I am adding IdentityServer3 on to an existing website (NopCommerce to be specific). It has it's own registration and authentication system, but we need to also offer OpenID Connect so that a back end application can be integrated. Calls to the back end need to have an id_token for the current user that the back end validates to confirm the identity.
I've found information about how to use an existing membership database to provide the user data for IdentityServer3 to check, however I am bit stuck on how to generate the id_token for each user. I guess the obvious answer is to replace the website login with IdentityServer, however that raises further issues for the rest of the project. Ideally I'd like the user to log in as normal and then call a method in IdentityServer to generate the id_token.
Is this possible? I've been hunting around, but can't find anything so far. The best I found was an answer to programmatically sign in to identityserver3. I think it's suggesting making a HTTP post to IdentityServer, but it feels kind of hacky.
I also found Implementing OAuth 2.0 and OpenId Connect provider using IdentityServer3 with existing login server and membership provider, but I have to admit it's assuming quite a bit of knowledge I don't have (yet).
My solution based on John C's answer worked using a NopCommece external authentication plugin, but I was unhappy with having to leave Nop to authenticate and register when IdentityServer was using the Nop database. Going via the external auth route seemed to be the only way to get an OpenID Connect id_token. After a break and some time to revisit the code though I found the following:
https://identityserver.github.io/Documentation/docsv2/configuration/serviceFactory.html
https://identityserver.github.io/Documentation/docsv2/configuration/serviceFactory.html
By implementing custom services, IdentityServer allows you to mess with the token creation and generation AND the dependency injection system it uses gives you access to instatiated versions of the default services.
Previously I had followed an answer which sent a username and password to the token endpoint. The OpenID specs say that this should only return the access_token, which is exactly what DefaultTokenService in IdenttyService does. By adding a CustomTokenResponseGenerator however, I was able to re-use the request to create and return an id_token too.
CustomTokenResponse class:
internal class CustomTokenResponseGenerator : ICustomTokenResponseGenerator
{
protected ITokenService _tokenService;
public CustomTokenResponseGenerator(ITokenService tokenService)
{
_tokenService = tokenService;
}
public Task<TokenResponse> GenerateAsync(ValidatedTokenRequest request, TokenResponse response)
{
var tokenRequest = new TokenCreationRequest
{
Subject = request.Subject,
Client = request.Client,
Scopes = request.ValidatedScopes.GrantedScopes,
//Nonce = request.AuthorizationCode.Nonce,
ValidatedRequest = request
};
var idToken = _tokenService.CreateIdentityTokenAsync(tokenRequest);
idToken.Wait();
var jwt = _tokenService.CreateSecurityTokenAsync(idToken.Result);
response.IdentityToken = jwt.Result;
return Task.FromResult(response);
}
}
How to inject the custom service in Startup.cs:
factory.TokenService = new Registration<ITokenService, TokenService>();
factory.CustomTokenResponseGenerator = new Registration<ICustomTokenResponseGenerator, CustomTokenResponseGenerator>();
When user logs-in into NopCommerce application, you can send an HTTP authorize request to identityserver. Make sure while sending the authorize request to idsrv you are using prompt=none, this way you will get the id_token or access_token without showing a consent to the user again if the user is already logged-in.
function getAuthorizeRequest() {
var url = global.appSettings.identityServerURL
+ "/connect/authorize?client_id=siteB&response_type=id_token token&redirect_uri="
+ global.appSettings.siteBUrl + "/Main/NopCommerceapp&scope=siteBscope openid email roles&prompt=none&nonce="76767xz676xzc76xz7c67x6c76"
return encodeURI(url);}
Checkout idsrv authorize endpoint https://identityserver.github.io/Documentation/docsv2/endpoints/authorization.html
I think your best solution would be to implement IdentityServer3 and get it reading from your existing Nop membership database. Then create a Web API app that runs on its own that utilizes your IdentityServer3 setup. Inside the Web API, you implement all the functionality that your back end app needs, reading and writing to the Nop database.
Keep your Nop frontend UI separate from your backend API. If you follow the two links below, you should be able to get something up and running pretty quickly.
Creating the simplest OAuth2 Authorization Server, Client and API
MVC Authentication & Web APIs

Renew access token and protect static files

I have question regarding setup of access token renewal/refresh. Our Setup:
Implicit flow
Angular SPA using bearer token for API
Thin MVC frontend serving cshtml containing SPA
Short access token (10min)
20 min Idsrv cookie sliding (used as activity timeout)
The application has to apply to some strict security rules and intellectual property.
We need to renew the access token before it expires and API returns 401.
I’ve looked at the oidc-client-js to handle that. But that would remove the option of authenticating the static files like we do today, since there would no longer be a cookie for the MVC app.
Is there a way of securing them, or is that just something that we have to accept when building a SPA with OpenID Connect?
If you would like to enforce authorization on static files then this needs to be done by server-side code. Since your client is using an MVC backend, my recommendation would be to use the Hybrid Flow in conjunction with the Katana OpenID Connect middleware. You may then pass on any tokens you would like to use from the server-side code to your SPA via your view (cshtml).
The middleware required is available on NuGet:
install-package Microsoft.Owin.Security.Cookies
install-package Microsoft.Owin.Security.OpenIdConnect
The following snippet allows for configuration in your OWIN pipeline (taken and slightly altered from this tutorial):
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44319/identity",
ClientId = "mvc",
RedirectUri = "https://localhost:44319/",
ResponseType = "code id_token",
Scope = "openid offline_access",
SignInAsAuthenticationType = "Cookies"
});
Using this flow, you are no longer given tokens immediately but will need to exchange the auth code returned ("code" grant type) for a pair of tokens being the:
access_token (the one you are already receiving in implicit flow)
refresh_token (this can be used at the token endpoint to renew the access_token)
The main things to note about the above configuration are response type and scope.
Response type is no longer just asking for tokens (implicit) but now asks for code.
Scope includes "offline_access" scope which will return the refresh_token.
Like this we have solved both your access token expiry problem and have begun to solve your static asset problem by moving authorization to the back-end. To help you with the next part I would need to know how you are serving your assets as there are different ways depending on whether you are using OWIN to serve or IIS.