Skip to content

Commit

Permalink
split up the auth function and changed the repo to be examples for fu…
Browse files Browse the repository at this point in the history
…ture auth stuff instead of just a spike
  • Loading branch information
tonykqt committed Oct 1, 2018
1 parent d8727c9 commit b3808ff
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 62 deletions.
18 changes: 8 additions & 10 deletions README.md
@@ -1,17 +1,15 @@
# Serverless Auth Test Repo
This is a spike on testing Cognito for authentication and AWS Lambda functions in Go.
Provider: AWS
# Serverless Auth
Collection of Cognito Auth examples using the User pool and Identity Pool.

This solution does not use the identity pool for giving the client access straight to the AWS resources.

It uses a custom authorizer function which looks at the JWT. The reason for this is that there was too much vendor lock in using Cognito and Identity Pools or took too much effort to get fine grained permissions.
The User Pool needs to be deployed first and then the JWKS URL needs to be updated in the auth function.

Instead, this will allow us to swap out authenticators e.g. Auth0 by using standard JWT verification.
# List of Auth Functions
Functions sit in `/functions` folder

The User Pool needs to be deployed first and then the JWKS URL needs to be updated in the auth function.
# AWS Cognito
|Name|Description|Requires Identity Pool?|
|---|---|:-:|
|auth|Provides basic authentication with a User Pool. Checks the aud claim, expiration and validity of the JWT.|N

**User Pool** manages Two factor auth either via Phone number or Email address. It also manages confirmation code to verify those fields.

# Deployment

Expand Down
73 changes: 21 additions & 52 deletions functions/auth/main.go
Expand Up @@ -2,68 +2,25 @@ package main

import (
"context"
"errors"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"fmt"
"github.com/dgrijalva/jwt-go"
"errors"
"github.com/lestrrat/go-jwx/jwk"
jwt "github.com/dgrijalva/jwt-go"
"github.com/serinth/serverless-cognito-auth/util"
"os"
"strings"
)

const ENV_REGION = `REGION`

func getKey(token *jwt.Token) (interface{}, error) {

jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json",os.Getenv(ENV_REGION), "<user pool id>")

fmt.Printf("JWKS URL: %s", jwksURL)
// TODO: cache response so we don't have to make a request every time
// must be set manually in API Gateway Authorizers (checkbox)
set, err := jwk.FetchHTTP(jwksURL)
if err != nil {
return nil, err
}

keyID, ok := token.Header["kid"].(string)
if !ok {
return nil, errors.New("expecting JWT header to have string kid")
}

if key := set.LookupKeyID(keyID); len(key) == 1 {
return key[0].Materialize()
}

return nil, errors.New("unable to find key")
}

// Help function to generate an IAM policy
func generatePolicy(principalId string, effect string, resource string) events.APIGatewayCustomAuthorizerResponse {
authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: principalId}

if effect != "" && resource != "" {
authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{
Version: "2012-10-17",
Statement: []events.IAMPolicyStatement{
{
Action: []string{"execute-api:Invoke"},
Effect: effect,
Resource: []string{resource},
},
},
}
}

authResponse.Context = map[string]interface{}{}
return authResponse
}
var REGION string
var USER_POOL_ID string
var APP_CLIENT_ID string

func handleRequest(ctx context.Context, event events.APIGatewayCustomAuthorizerRequest) (events.APIGatewayCustomAuthorizerResponse, error) {
splitToken := strings.Split(event.AuthorizationToken, "Bearer ")
tokenString := splitToken[1]

token, err := jwt.Parse(tokenString, getKey)
token, err := jwt.Parse(tokenString, util.GetKey(REGION, USER_POOL_ID))
if err != nil {
return events.APIGatewayCustomAuthorizerResponse{}, errors.New(fmt.Sprintf("Error: Invalid token with error: %v", err))
}
Expand All @@ -77,7 +34,19 @@ func handleRequest(ctx context.Context, event events.APIGatewayCustomAuthorizerR
fmt.Printf("%s\t%v\n", key, value)
}

return generatePolicy(claims["sub"].(string), "Allow", event.MethodArn), nil
badClaims := claims.Valid()

if badClaims != nil || !claims.VerifyAudience(APP_CLIENT_ID, true) {
return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Invalid Token.")
}

return util.GenerateLambdaInvokePolicy(claims["sub"].(string), "Allow", event.MethodArn), nil
}

func init() {
REGION = os.Getenv("REGION")
USER_POOL_ID = os.Getenv("USER_POOL_ID")
APP_CLIENT_ID = os.Getenv("APP_CLIENT_ID")
}

func main() {
Expand Down
3 changes: 3 additions & 0 deletions serverless.yml
Expand Up @@ -28,6 +28,9 @@ package:
functions:
auth:
handler: bin/auth
environment:
APP_CLIENT_ID: ""
USER_POOL_ID: ""
privateFunc:
handler: bin/privateFunc
events:
Expand Down
33 changes: 33 additions & 0 deletions util/jwks.go
@@ -0,0 +1,33 @@
package util

import (
"errors"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/lestrrat/go-jwx/jwk"
)

func GetKey(region string, userPoolId string) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", region, userPoolId)

fmt.Printf("JWKS URL: %s", jwksURL)
// TODO: cache response so we don't have to make a request every time
// must be set manually in API Gateway Authorizers (checkbox)
set, err := jwk.FetchHTTP(jwksURL)
if err != nil {
return nil, err
}

keyID, ok := token.Header["kid"].(string)
if !ok {
return nil, errors.New("expecting JWT header to have string kid")
}

if key := set.LookupKeyID(keyID); len(key) == 1 {
return key[0].Materialize()
}

return nil, errors.New("unable to find key")
}
}
25 changes: 25 additions & 0 deletions util/lambdaInvokePolicy.go
@@ -0,0 +1,25 @@
package util

import (
"github.com/aws/aws-lambda-go/events"
)

func GenerateLambdaInvokePolicy(principalId string, effect string, resource string) events.APIGatewayCustomAuthorizerResponse {
authResponse := events.APIGatewayCustomAuthorizerResponse{PrincipalID: principalId}

if effect != "" && resource != "" {
authResponse.PolicyDocument = events.APIGatewayCustomAuthorizerPolicy{
Version: "2012-10-17",
Statement: []events.IAMPolicyStatement{
{
Action: []string{"execute-api:Invoke"},
Effect: effect,
Resource: []string{resource},
},
},
}
}

authResponse.Context = map[string]interface{}{}
return authResponse
}

0 comments on commit b3808ff

Please sign in to comment.