In every API call to your backend server, you should include the access_token in the header of your requests. You need to verify the access_token on each endpoint that you deem necessary. Usually, you would use a middleware so it automatically handles the verification for each of your routes.
Some good JWT middleware libraries that you can use:
The access token is a JWT Token, and it's signed using Asymmetric Signing Algorithm ES256. This means, unlike symmetric JWT tokens that are signed and verified using the same secret key, this asymmetric JWT Token is signed using a secret key that only Cotter knows, but can be verified using a public key that you can find here.
Checking other attributes of the user
If you want to check the user's email or phone number before allowing access, for example, you want to allow only emails with a specific domain to log in, you should do the check here.
take the key with kid = SPACE_JWT_PUBLIC:8028AAA3-EC2D-4BAA-BE7A-7C8359CCB9F9
Make sure you take the keys from this endpoint, and cache when necessary, but don't hard-code it. The key may change.
Examples
Access tokens are usually included in oauth_token.access_token in the responses from the SDK, or in the Authorization Header.
// Install Dependencyyarn add cotter-node// Validate tokenvar cotterNode =require("cotter-node");var cotterToken =require("cotter-token-js");// Validate access tokenconstaccess_token=oauth_token.access_token;try {var valid =awaitcotterNode.CotterValidateJWT(access_token);} catch (e) {// Not Validconsole.log(e)}// Read access tokenlet decoded =newcotterToken.CotterAccessToken(access_token);console.log(decoded);// Check that `aud` is your API KEYconstaudience=decoded.getAudience();if (audience !==YOUR_API_KEY_ID) {throw"Audience doesn't match"}// (Optional) Checking other attributes of the user// Example, only allow my company domain to loginconstemail=decoded.getIdentifier();if (email.split("@")[1] !=="cotter.app") {throw"Please use cotter business email instead of a personal email";}
# Install Dependenciespip install cotterpip install -U flask-cors# Add a flask Endpointfrom flask import Flaskfrom flask import requestfrom flask_cors import CORSfrom cotter import validateapp =Flask(__name__)CORS(app)@app.route('/login', methods=['POST'])deflogin(name=None): req = request.get_json();# Getting access token and validate it token = req["oauth_token"]["access_token"] access_token_decoded = validate.validate_access_token(token, API_KEY_ID)# User Authenticated!# a) Either use Cotter's Access Token for your entire API authorization# OR# b) You can Generate your JWT Tokens or other session management herereturn resp;
<?php require__DIR__.'/vendor/autoload.php';useJose\Component\Core\AlgorithmManager;useJose\Component\Core\JWK;useJose\Component\Signature\Algorithm\ES256;useJose\Component\Signature\Serializer\JWSSerializerManager;useJose\Component\Signature\Serializer\CompactSerializer;useJose\Component\Signature\JWSVerifier;// The algorithm manager with the HS256 algorithm.$algorithmManager =newAlgorithmManager([newES256(),]);// We instantiate our JWS Verifier.$jwsVerifier =newJWSVerifier( $algorithmManager);// The serializer manager. We only use the JWS Compact Serialization Mode.$serializerManager =newJWSSerializerManager([newCompactSerializer(),]);$http =newGuzzleHttp\Client();$response = $http->request('GET','https://www.cotter.app/api/v0/token/jwks', []);$keys =json_decode((string) $response->getBody(),true);$jwk =newJWK($keys["keys"][0]);$token ="eyJhbGciOiJFUzI1NiIsImtpZCI6IlNQQU...";// We try to load the token.$jws = $serializerManager->unserialize($token);// We verify the signature. This method does NOT check the header.// The arguments are:// - The JWS object,// - The key,// - The index of the signature to check. See $isVerified = $jwsVerifier->verifyWithKey($jws, $jwk,0);// continue your logic here?>
packagemiddlewareimport ("encoding/json""errors""fmt""io/ioutil""net/http""strings""github.com/google/uuid""github.com/labstack/echo/v4""gopkg.in/square/go-jose.v2""gopkg.in/square/go-jose.v2/jwt")// 👇 Enter your API KEY ID hereconst API_KEY_ID ="YOUR_API_KEY_ID"const JWKSURL ="https://www.cotter.app/api/v0/token/jwks"const JWKSLookupKeyID ="SPACE_JWT_PUBLIC:8028AAA3-EC2D-4BAA-BE7A-7C8359CCB9F9"funcgetKey() ([]byte, error) {// Fetch the JWT Public Key from the URL resp, err := http.Get(JWKSURL)if err !=nil {returnnil, err } body, err := ioutil.ReadAll(resp.Body)if err !=nil {returnnil, err }// Parse the response into our keys struct keyset :=make(map[string][]map[string]interface{}) err = json.Unmarshal(body, &keyset)if err !=nil {returnnil, err }// It's a Key Set = there might be multiple keys// Find the key with kid = JWKSLookupKeyIDiflen(keyset["keys"]) <=0 {returnnil, errors.New("Key set is empty") }for _, k :=range keyset["keys"] {if k["kid"] == JWKSLookupKeyID { key, err := json.Marshal(k)if err !=nil {returnnil, err }return key, nil } }returnnil, errors.New("Cannot find key with kid")}// validateClientAccessToken validates access token created abovefuncvalidateClientAccessToken(accessToken string) (map[string]interface{}, error) { tok, err := jwt.ParseSigned(accessToken)if err !=nil {returnnil, errors.New("Fail parsing access token") } keys, err :=getKey()if err !=nil {returnnil, err } key :=jose.JSONWebKey{} key.UnmarshalJSON(keys) token :=make(map[string]interface{})if err := tok.Claims(key, &token); err !=nil {returnnil, errors.New("Fail parsing access token to claims") }// Check that the aud is our API KEY ID apiKeyID, ok := token["aud"].(string)if!ok {returnnil, errors.New("fail asserting aud from jwt.MapClaims") }if apiKeyID != API_KEY_ID {returnnil, errors.New("Invalid aud, not meant for this api key id") }return token, nil}// This middleware assumes that your API endpoint looks like this:// GET https://something.com/user/1// Authorization: Bearer <access_token> 👈 We want to verify this// CotterAuth is used to authenticate cotter access_tokensfuncCotterAuth(next echo.HandlerFunc) echo.HandlerFunc {returnfunc(ctx echo.Context) error {// get the access token from auth header reqToken := ctx.Request().Header.Get("Authorization") splitToken := strings.Split(reqToken, " ")iflen(splitToken) !=2 {return errors.New("authorization header malformed") } tokenString := splitToken[1]// Validate that the access token and signature is valid token, err :=validateClientAccessToken(tokenString)if err !=nil {return echo.NewHTTPError(http.StatusBadRequest, err.Error()) }// Read other claims, such as sub = Cotter User ID uIDStr, ok := token["sub"].(string)if!ok {return echo.NewHTTPError(http.StatusBadRequest, "fail asserting sub from jwt.MapClaims") } cotterUserID, err := uuid.Parse(uIDStr)if err !=nil {return echo.NewHTTPError(http.StatusBadRequest, "invalid user id format in jwt claims") } fmt.Println("User logging in = ", cotterUserID)returnnext(ctx) }}