IDaaS | Easily replace AWS Cognito with Authing + Lambda

Posted by Square1 on Mon, 26 Aug 2019 03:12:11 +0200

Although Amazon Web Services(AWS) is the world's largest market share cloud computing vendor, its products are not perfect either. Cognito (AWS's Identity Certification Solution) and its accompanying Chinese documentation are a negative textbook that is surprisingly difficult to use.Of course, in addition to being not easy to use, there are also problems such as slow access and inappropriateness to the Chinese market.

Home-made Authin can solve many problems with Cognito. First, take a look at Authin's introduction:

Authin is an identity service provider that provides enterprise-level identity authentication and management solutions, customer distribution education, IoT, the Internet, and business.

Lambda is a Function-as-a-Service (FaaS) platform provided by AWS.Lambda and AWS ecology are tightly integrated, and with Lambda, developers can use all the resources within the AWS ecology.For example, we can create a Lambda function that allows users to log in through Cognito (of course, this article lets users log in using Authing) and then call another Lambda function that uploads files to S3, the storage service for AWS.

One benefit of such platforms (now more commonly referred to as Serverless, no server architecture) is that they allow developers to focus on business development without worrying about infrastructure.

FaaS, or Serverless, is gaining market attention because this type of platform allows developers to stop focusing on infrastructure.This article "What is serverless" explains in detail the benefits of "Serverless Computing" and "Serverless Computing", and it is recommended that you read it.

The main purpose of this article is to show how to replace AWS Cogito with Authing + Lambda at the following address: https://sample.authing.cn/aws.

In addition, Authin follows the OIDC specification, so this article will use OIDC to authenticate. If you don't know what an OIDC is, search the Internet first.

First confirm the user's process

  1. Open the page: sample.authing.cn/aws/;
  2. Click Login to login, then jump to Authin's login page (the secondary domain name applied);
  3. Enter the account password for login, or register first if there is no account password;
  4. After successful login, return to the page opened in the first step and display the logged-in user's avatar;
  5. At this point the user can see Private information returned from the AWS Lambda request;

Create an Authin application

If you haven't registered Authing yet, click here to register. After registration is complete, follow these steps to create an Authing application.

1. Create an application

2. Fill in the basic information, choose the type of application Web application

3. Once created, it will go to the application home page (empty)

Create OIDC Application

Creating an application is like having a pool of users. Next, you can create an OIDC application to authorize other programs (your own or other third-party programs) to access your pool.

4. Click "Third Party Login" to start creating OIDC applications

5. Select the OIDC Application tab and click Create OIDC Application.

6. Fill in the application name and authentication address and check id_token token

It is important to note here that the authentication address used to create the OIDC application will be generated by Authin as a secondary domain name (HTTPS-enabled) and cannot be duplicated. The callback URL will fill in your own callback address. Here I am using https://authing.cn Note that callback URL s are not allowed to be localhost in the OIDC protocol, use the proxy tool to debug.

7. Click OK to see that we have the first authorized application based on the OIDC protocol

Once created, you can access lambda.authing.cn and you will see an error reported. Don't be afraid, this is because we initiated an incorrect authorization link.

8. Error accessing lambda.authing.cn

Continue to see how the correct authorization request was initiated.

Initiate Authorization Request

Like most OAuth applications, authorization links for OIDCs need to be stitched (which should be easy to understand if you have developed WeChat applications). Authing OIDC applications have authorization links that conform to standard specifications in the following format:

https://lambda.authing.cn/oau... ; To apply ID>&redirect_uri=<callback URL, you must be exactly the same as the platform configuration>&scope=openid profile&response_type=<OIDC mode, divided into several >&state=<one random string to guard against CSRF attacks>

For detailed parameters, click here to view them.

For example:

https://lambda.authing.cn/oau...://authing.cn&scope=openid profile&response_type=id_token token&state=jacket

For simplicity, here our response_type is set to "id_token token", so there is no need to exchange "code" for token, which is directly attached to the callback address.

9. If your authorization link is correct, you should see a login window like the one shown above

If your authorization link is correct, you should see a login window like the one shown above, which is also used by your end users who will log in from here and call back to your configured callback URL.

You can try registering an account and logging in. After logging in, you can see the status in the console.

10. Successful registration

11. Authorization page after login

12. User data observed in the console

You should see a callback to your fill-in URL after your successful login, along with a number of parameters, which we'll show you how to use next.

Get user information

When you call back to the redirect_uri configured in the console, the following information is attached:

{
    "id_token": "JWT_TOKEN",
    "access_token": "JWT_TOKEN",
    "expires_in": "3600",
    "token_type": "Bearer",
    "state": "jacket",
    "session_state": "644d7b324ba61d517fdedd28b5b6e365d78f2a8178f2ee742474d5b57a99eb3f"
}

You can see that it contains access_token and id_token, where access_token helps you get user information from the Authin backend, and id_token contains basic information, which you need to get through access_token if you want to get a user's avatar.

Let's start with an example of id_token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJzdWIiOiI1Y2MyYTg1MTFiYmFmMDRmOTNjZTQ4OWYiLCJub25jZSI6IjE4MzEyODkiLCJzaWQiOiI5MzkwZDA1ZC01ZTM3LTQ3ZWUtODJjNi1jNTQ1ZjA2ODhhMDAiLCJhdF9oYXNoIjoiNmxZMGRXajZYUTY0aExWdHAtR2tEdyIsInNfaGFzaCI6IlZVOU5QYV9JQ0VTSEdxRmxUZ3A2LUEiLCJhdWQiOiI1Y2MyYjU0OGQxNGM3NDJkYjg5M2JhNTUiLCJleHAiOjE1NTYzNjY0ODksImlhdCI6MTU1NjM2Mjg4OSwiaXNzIjoiaHR0cHM6Ly9vYXV0aC5hdXRoaW5nLmNuL29hdXRoL29pZGMifQ.Qc_OMqMf6_wwzW2SsEgEtiaGr3ZY1FWHnRrMU2M7LADGlNpq_pvPrFxAVsR2j-BFr1y48M-Trvq6yAu4_ZOUBHPtIIpoQ5W2bnABytUV693ZcwNlf9CCiLc-k0LG3o1U-BmiH3L6NAV7aKGsfVHS8toiNbVDuimPVdYJsRrF2C1jj1meM1K8FBVwqozXm6YtB--u3sqY4IszHnd5PMEWguLsOkpZJIh7xWeYPpVQ5WKfx0cA8rB_T2puSCbeaUVhgIwNADy06qBqXhUOiA4gdcNbHtx7tvGZMxzMC3rdjpXoZk89Duh3O5tHlMtaBlidJGYavUSjVl7potESecSlBg

Parsing with jwt.io yields the following results:

{
  "sub": "5cc2a8511bbaf04f93ce489f",
  "nonce": "1831289",
  "sid": "9390d05d-5e37-47ee-82c6-c545f0688a00",
  "at_hash": "6lY0dWj6XQ64hLVtp-GkDw",
  "s_hash": "VU9NPa_ICESHGqFlTgp6-A",
  "aud": "5cc2b548d14c742db893ba55",
  "exp": 1556366489,
  "iat": 1556362889,
  "iss": "https://oauth.authing.cn/oauth/oidc"
}

It contains fields such as the issuance time (iat), expiration time (exp) to determine if the user has been certified. In the OIDC specification, JWT uses the secret s of the OIDC application to issue, requiring the developer to continue executing the developer's own business after back-end verification (which we will do in Lambda).Business process.

Take another look at the access_token example:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJqdGkiOiJza0p-bTNaYmZsTjVxVGEzR2J2YlMiLCJzdWIiOiI1Y2MyYTg1MTFiYmFmMDRmOTNjZTQ4OWYiLCJpc3MiOiJodHRwczovL29hdXRoLmF1dGhpbmcuY24vb2F1dGgvb2lkYyIsImlhdCI6MTU1NjM2Mjg4OSwiZXhwIjoxNTU2MzY2NDg5LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIiwiYXVkIjoiNWNjMmI1NDhkMTRjNzQyZGI4OTNiYTU1In0.Uf3YK4D9HL-G71hkA4cWt5kitDo5rNgwVA9Vqlv4RjAILNDTylYWtkacKJpLcOSS81ivaNpDVNYYzBSoyN-eMH80VhArPUre74F9SHdonA-IVFVPT0DHRtOAJI9kqDW4tgTXhZeZMUm-MCjVjR-q8XrayXaqrC5Hu5W3D1N-K_jZOlwxzIBf51nuC4NMvSI_wPpYj2WPzGxFwpfTCEbnhj5RO0CcThRpC3EdmpbtcJqStd7AZQhkLyTb1TQLHJOel8DSxLnLnoIU0rZXsodK6EjE_oqRLagetNXF1cKfRmnGFaAKZKqgvHc527S_CVkgXIwcHBRmDeqo93CCId_hmQ

Parsing with jwt.io yields the following results:

{
  "jti": "skJ~m3ZbflN5qTa3GbvbS",
  "sub": "5cc2a8511bbaf04f93ce489f",
  "iss": "https://oauth.authing.cn/oauth/oidc",
  "iat": 1556362889,
  "exp": 1556366489,
  "scope": "openid profile",
  "aud": "5cc2b548d14c742db893ba55"
}

You can see that access_token has much less information than id_token. Here is an introduction in English that explains the difference between access_token and id_token:

ID Tokens vs Access Tokens. The ID Token is a security token granted by the OpenID Provider that contains information about an End-User. This information tells your client application that the user is authenticated, and can also give you information like their username or locale.You can pass an ID Token around different components of your client, and these components can use the ID Token to confirm that the user is authenticated and also to retrieve information about them.Access tokens, on the other hand, are not intended to carry information about the user. They simply allow access to certain defined server resources. More discussion about when to use access tokens can be found in Validating Access Tokens.

Simply put, id_token tells you that the user has been authenticated, and access_token is a credential that allows you to access the resource server (in this case, Authing).

You can also see that idtoken contains less information, and if you want to get more information, you need to use access_token to get it.It is also very simple to get it by sending a GET request with access_token to the following links, such as:

$ curl https://users.authing.cn/oauth/oidc/user/userinfo?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQy...balabala...verylong...

You can get information such as id, after which you can store the ID in your own database to complete your actual business.

{
    "sub":"5cc2a8511bbaf04f93ce489f",
    "nickname":"",
    "picture":"https://usercontents.authing.cn/authing-avatar.png"
}

The JSON above is a result of exchanging access_token for user data.

Okay, now that we have the Token, we need to verify its validity in Lambda and display different information on the front end.

Writing Lambda functions

Writing Lambda functions is a recommended CLI for Serverless, and writing functions in the AWS console can be painful.

In the meantime, you can see the full code here.

Lambda does three main things in this article:

  1. Authenticate id_token to see if the user has been authenticated;
  2. Provide a Public API that can be accessed directly;
  3. Provide a Private API that needs to be authenticated and accessed;

Authenticate id_token

The authentication id_token first needs to know the secret of the OIDC application, which can be found in the details of the OIDC application in the Authing console:

The signature of id_token at the time of issuance is this secret, so the library jsonwebtoken can be used directly in JavaScript to verify the legality of id_token (see: Verify Token legality for details).

Install jsonwebtoken in the console:

$ npm install jsonwebtoken --save

P.S. packages are packaged and uploaded to the AWS Lambda runtime when they are introduced in lambda.

const jwt = require('jsonwebtoken');

// Policy helper function 
// This is the template code provided by AWS, no modification is required here
const generatePolicy = (principalId, effect, resource) => {
  const authResponse = {};
  authResponse.principalId = principalId;
  if (effect && resource) {
    const policyDocument = {};
    policyDocument.Version = '2012-10-17';
    policyDocument.Statement = [];
    const statementOne = {};
    statementOne.Action = 'execute-api:Invoke';
    statementOne.Effect = effect;
    statementOne.Resource = resource;
    policyDocument.Statement[0] = statementOne;
    authResponse.policyDocument = policyDocument;
  }
  return authResponse;
};

// Reusable Authorizer function, set on `authorizer` field in serverless.yml
module.exports.auth = async (event, context, cb) => {
  if (event.authorizationToken) {
    // remove "bearer " from token
    const token = event.authorizationToken.substring(7);

    try {
        let decoded = jwt.verify(token, 'YOUR_OIDC_APP_SECRET'),
          expired = (Date.parse(new Date()) / 1000) > decoded.exp;
        if (expired) {
          cb('Unauthorized, Login information has expired.');
        }else {
          cb(null, generatePolicy('user', 'Allow', event.methodArn));
        }
      } catch (error) {
        cb('Unauthorized');
      }
  } else {
    cb('Unauthorized');
  }
};

Public API

// Public API
module.exports.publicEndpoint = (event, context, cb) => {
  cb(null, { message: 'Welcome to our Public API!' });
};

Private API

// Private API
module.exports.privateEndpoint = (event, context, cb) => {
  cb(null, { message: 'Only logged in users can see this' });
};

serverless.yml

service: serverless-authorizerprovider: 
 name: aws runtime: nodejs8.10functions:  auth:    handler: handler.auth  getUserInfo:    handler: handler.getUserInfo    events:      - http:          path: api/userInfo          method: get          integration: lambda          cors: true    
  publicEndpoint:    handler: handler.publicEndpoint    events:      - http:          path: api/public          method: get          integration: lambda          cors: true  privateEndpoint:    handler: handler.privateEndpoint    events:      - http:          path: api/private          method: get          integration: lambda          authorizer: auth # See custom authorizer docs here: http://bit.ly/2gXw9pO          cors: true

This file can be used to configure routes that require authentication, such as the privateEndpoint in the code above, where authorizer is configured as the auth function.

Refer to: https://github.com/authing/au...

Test Lambda

We need to test after we have written the code.

Lambda supports direct local testing using the following commands:

$ sls invoke local -f auth --data '{"authorizationToken": "Bearer <id_token>"}'

If the local test returns the following information, the validation is successful:

{
    "principalId": "user"
}

Deploy Lambda

$ serverless deploy

Once deployed, you get three links, which are three functions of the code above.

Use curl or postman to bring the id_token of the OIDC login into the header's Autohorization to see the results, such as:

$ curl --header "Authorization: <id_token>" <endpoint>
curl <endpoint/dev/api/public> - Should work! Public!
curl <endpoint/dev/api/private> - Should not work
curl --header "Authorization: <id_token>" <endpoint/dev/api/private> - Should work! Authorized!

Finally, add relevant information to our front-end, you should see the following information after clicking Login:

Online experience address: sample.authing.cn/aws/

Enjoy!

What is Authing?

Authin provides professional authentication and authorization services.
We provide developers and businesses with the authentication modules they need to secure their applications, so they don't need to be security experts.
You can connect any platform's apps to Authing (new or old), and you can customize how the app logs in (e.g. mailbox/password, SMS/Authentication code, scanner login, etc.).
Depending on the technology you use, you can select our SDK or invoke related API s to connect to your application.When a user makes an authorization request, Authin helps you authenticate them and return the necessary user information to your application.

<div align=center>Authin's position in application interaction</div>

Welcome to Authin Technology Column

Topics: Javascript Lambda AWS curl github