See access-control.md for information about about authn/authz and Zinc access control.
Please feel free to create Github issues, pull requests or discussion topics regarding these or other security considerations you'd like to talk about.
Zinc does not use the recommended "PKCE" grant type (I think only Google implements it anyway). Zinc generally uses the "Authorization Code" grant type, with the "confidential client" part implemented on the server-side via AWS Lambda, see oauth-oidc.md.
None of the direct authentication methods use anything except the minimal code that works to sign-in (no CSRF tokens, no nonces, etc.)
This is done for cost, AWS App Config and Secrets Manager cost money I don't want to pay for a demo code base.
If you're adapting any of this code for real, you should not store secrets in SSM.
The various direct sign-in methods just pass through the id_token from the provider back to the client and then it's left to the "authorize" logic in the ZincApi to verify them appropriately.
Cognito, on the other hand, generates a cognito-specific user-id and returns a
new id_token
JWT based on that to the client. Cognito maps the user-id
to the IdProvider's sub
via a data store (i.e. the user-pool) so that
subsequent authentications via that IdProvider map to the same cognito user-id.
A better design for the direct authentication methods would be for each sign-in
method to normalize to a custom Zinc id_token format and signature algorithm.
This will simplify the authz logic and reduce the configuration of the authz
lambda (more secure too, since then ZincApi lambda doesn't need to be able to
read all the different client_secret
values, which is super-dodgy).
Turns out AWS KMS has an always-free tier, so could even use RS256 for securing the id_token. It would be a chunk of implementation - but definitely worth the effort in a real system.
The various lambda handlers all use the method DANGERouslyLogEvent()
, which
logs all event parameters to CloudWatch.
See LambdaEvent.ts
The accessToken is stored in local-storage, not as a secure http cookie - so there's no implementaiton of CSRF prevention.
- I'm personally fine with this, but some people think it's "not the way"
- if you want to use a cookie, you'll need to make sure it's secure, http-only, and implement a CSRF prevention strategy
PII = Personally Identifiable Information
The accessToken contains PII (email
claim), which is bad practice for an
access token, particularly when storing it in client storage.
This is pure laziness on my part, the email is used for two things:
- used when logging problems, facilitates diagnosis and monitoring
- solution: implement a debug lookup utility to add the email to logs
- solution won't be viable sometimes and you'll just have to use the userId to figure things out
- email isn't even exact, you have to log the userId anyway because it's possible for a user to use multiple IdP's using same email address.
- used to display the email in the "Account" widgent (top right of screen)
- solution: implement a proper user details lookup endpoint instead of
using the claim directly
- the endpoint for this does exist now (
/readUser
), I just haven't refactored
- the endpoint for this does exist now (
- solution: implement a proper user details lookup endpoint instead of
using the claim directly
The direct login implementation leaks the unique-ID that providers use to identify a user - e.g. if you login and go to the user list, you can find out my Github and Google unique id
Possbile threat vector: if attacker already knows the user's Google unique id
but doesn't know if that person uses Zinc,
the zinc authentication process could unintentionally leak the knowledge that
the user has a Zinc account.
Or conversely, if an attacker could gain an
id_token (unlikely, Zinc only uses them ephemerally) or an accessToken (they're
in local storage, so more vulnerable) - they could analyse the id to figure out
which IdProvider it's for, and then they also know that user's IdProvider id
(doubt that's a particular threat, those things are all over the internet).
The backend authentication and authorization code needs lots more logic (and testing) of checking and verifying claims, scopes, etc.
Needs to test algorithm stuff:
- prove that we don't accept
alg:none
- prove that we don't accept
alg:hs256
where the token is actually signed with the public key of an RS256 pair (yup, there've been multiple library implementations that've done that)
Should use a cryptographic primitive designed for the task.
Note that, for this repo, I was only interested in learning how to use Cognito to authenticate users. For this use-case, I'm not interested in using AWS resources directly or managing authorization via AWS IAM - so Zinc does not use Cognito identity-pools, only user-pools.
Note that if you want to use Cognito but not do your own authorization, you will need to use Cognito identity-pools. The Cognito "id token" expiry can only be set up to 24 hours, but identity-pool "access tokens" can be refreshed for up to 30 days.
It's really not a good idea to keep id tokens around for a long time - especially don't store them in local storage, cookies, etc.