Skip to content

Instantly share code, notes, and snippets.

@wojnar48
Last active November 25, 2021 18:25
Show Gist options
  • Save wojnar48/a07b8cf85aa15736a6fad96a6d5632e6 to your computer and use it in GitHub Desktop.
Save wojnar48/a07b8cf85aa15736a6fad96a6d5632e6 to your computer and use it in GitHub Desktop.
Notes

OpenID Connect

OpenID Connect is a thin layer on top of OAuth 2.0 that provides authorization (identity) on top of authentication (OAuth 2.0). OIDC introduced the identity token (JWT) which contains info about the user.

Allows users to grant access to resources without sharing the password and with revoke-ability.

Implicit Flow

NOTE: Flows are called grants in the specification.

The precursor to Authorization Flow. No longer used because tokens were returned directly to the browser and could be leaked. Designed for a time when JS could not access browser history or local storage.

In addition, most providers did not allow the CORS POST to /token endpoint that is needed in the Authorization Code flow.

Sample request to the authorize call to the Auth Server:

https://dev-micah.okta.com/oauth2/default/v1/authorize?
client_id=0oapu4btsL2xI0y8y356
&redirect_uri=http://localhost:8080/callback
&response_type=id_token token // What identifies Implicit Flow (ID Token + Access Token returned).
&response_mode=fragment
&state=SU8nskju26XowSCg3bx2LeZq7MwKcwnQ7h6vQY8twd9QJECHRKs14OwXPdpNBI58
&nonce=Ypo4cVlv0spQN2KTFo3W4cgMIDn6sLcZpInyC40U5ff3iqwUGLpee7D4XcVGCVco
&scope=openid profile email

Sample redirect back from the Auth Server:

http://localhost:8080/authorization-code/callback#
id_token=eyJraWQiOiJUTmJhREFfbDBua3RWRHdaMi12RlNUNENEVTlQM182VnBuZ3FRWmVEM0hvIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHUydmhvM2tmOWR2YnhPaDVkNyIsIm5hbWUiOiJTenltb24gV29qbmFyIiwiZW1haWwiOiJzenltb25yd29qbmFyQGdtYWlsLmNvbSIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtMzk5MDc0MTUub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2EydmxpMzRqUGxDYjVlZTVkNyIsImlhdCI6MTYzNzg2MzQ1OCwiZXhwIjoxNjM3ODY3MDU4LCJqdGkiOiJJRC5kaW44UEJCcjZYN1ZPbmhaSF9DV2hMd3B1SlJHWm1CVVBjNElMS1pDVUI0IiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG8ydmhrcnMzQXVLU29ZNTVkNyIsIm5vbmNlIjoibGtQOGxNeDFuZG5nZE54ZXQ1S1RXbE9kcnFodmhhYmNVMUdmM1J2bHo4TTNhR1JCbEQ4VFI4NEdaM3g1MmFCNSIsInByZWZlcnJlZF91c2VybmFtZSI6Indvam5hcjQ4QGdpdGh1Yi5va3RhaWRwIiwiYXV0aF90aW1lIjoxNjM3ODYzNDU3LCJhdF9oYXNoIjoiS0pJS3g5MjAzbXF2YXFXZC1aZDNoUSJ9.fbAyYldEY27isRKG30Y6rJbgcpAjfZxXGMnLPfHN-nCaGIwNkr97NLb4BpC5vZZZTKQvh8bEzIFSxDn_gdlbxrrmVys0LRW_LYAcS-NJ37ULSA6YVx7LMvRP4ZwdhmehXHZlYLjm4vlMhKaG-cMkPyhjGWzi18J_C6GRxiGsu6bl8Doff4xaQOH0YMUMCBtgdsHZx_6cK7zFjM4PIGm7wQWAcejys8qevrWZlseic8eGCjE4YsPx0S07FOFA3FbD-EA3Ps7KX4kNbODfP9zoklo_hfBdwP9HSOtHwbp7s9rcMbKZe-M_j1IkcwRcGlyjiQO_TbvcGCYvKUwh-BdClA
&access_token=eyJraWQiOiJUTmJhREFfbDBua3RWRHdaMi12RlNUNENEVTlQM182VnBuZ3FRWmVEM0hvIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULldiRGJaOVA1VDhNcHR4cG9qR2tYMC1mRU4zcTZWdHB4MTF4LU80VFFaa2ciLCJpc3MiOiJodHRwczovL2Rldi0zOTkwNzQxNS5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6ImFwaTovL2RlZmF1bHQiLCJpYXQiOjE2Mzc4NjM0NTgsImV4cCI6MTYzNzg2NzA1OCwiY2lkIjoiMG9hMnZsaTM0alBsQ2I1ZWU1ZDciLCJ1aWQiOiIwMHUydmhvM2tmOWR2YnhPaDVkNyIsInNjcCI6WyJlbWFpbCIsInByb2ZpbGUiLCJvcGVuaWQiXSwic3ViIjoid29qbmFyNDhAZ2l0aHViLm9rdGFpZHAifQ.spsbBM-Q5mdvtYJaAuRAxzYjD-YVsKc4KBC8FfVPURTr6S4QYHqjpL0HUg084Nn9phdFZznIH845lD7lt3P83eD9_um271pujEIplfqps1fN4s4mNzsNb9aX1sYH9XpMYfqVMW1w0zpS1X35YmeAE4f3M8GXIUVcQjKHF-mqKDAUpxCkQI1rkwKyce4kPsXfapzPsgCbjDkbxNaLIm-oamFZYanKjX9TG3gYdbUO2P9RWcNMaYCcXtbZyaFPGmbo_lUZmxM-OR5Z-YzJXKSCMAWLLRbVna2Vgbytl4yOsG21XNMb5AcVHv4Du6qQjdNMFNhO0Y_bi0E1mc-M-sKNtg
&token_type=Bearer
&expires_in=3600
&scope=email+profile+openid
&state=PSILbZVle8BxGvK3sU2ID1z8rDVA2ZdPWW726JNa75svd4nUMKSFWhDFpCdHFb33

Authorization Code with PKCE

Initially used by native and mobile apps because at the time most browsers and providers were not capable of supporting PKCE.

Allows apps to use the most secure OAuth 2.0 flow in public and untrusted clients. It does this by doing some pre-req setup work before the flow and some verification at the end to effectively utilize a dynamically generated secret. This is crucial bc. it is not safe to have a fixed secret in a public client (like a SPA app).

PKCE works by:

  • Having the app generate a random value before going through the flow called a Code Verifier.
  • Hashing the Code Verifier to obtain a Code Challenge.
  • Kicks off flow with Code Challenge included in the query string for the request to the Auth Server.
  • The Auth Server stores the Code Challenge and after the user authenticates, redirects back to the app with an authorization code.
  • App makes the request to exchange the authorization code for tokens but instead of a fixed secret, it sends the Code Verifier.
  • The Auth Server hashes the Code Verifier and compares it with the previously stored hashed value before returning the tokens.

Sample request to Auth Server:

https://dev-micah.okta.com/oauth2/default/v1/authorize?
client_id=0oapu4btsL2xI0y8y356&
redirect_uri=http://localhost:8080/callback&
response_type=code& // Identifies Authorization Code flow (response will be an authorization code).
response_mode=fragment&
state=MdXrGikS5LACsWs2HZFqS7IC9zMC6F9thOiWDa5gxKRqoMf7bCkTetrrwKw5JIAA&
nonce=iAXdcF77sQ2ejthPM5xZtytYUjqZkJTXcHkgdyY2NinFx6y83nKssxEzlBtvnSY2&
code_challenge=elU6u5zyqQT2f92GRQUq6PautAeNDf4DQPayyR0ek_c& // Included by Okta in PKCE extension
code_challenge_method=S256& // Included by Okta in PKCE extension
scope=openid profile email

Sample redirect from Auth Server:

http://localhost:8080/authorization-code/callback#
code=OJ6P8kNgTX9SWCPZe_FzfgqlSY095_w3Bg5mHzeyC6U
&state=8ubaBk4F0F7xZTSluxj5KDIW5BRuqIykjPKFjZACV1g3DK2z9RBPBdCe4PCBEUTh

Even though the authorization code is still returned to the browser it has 3 advantages:

  1. It is very short lived (60 seconds in Okta).
  2. It can only be used once in exchange for tokens.
  3. It can only be used in exchange for tokens with a secret (the Code Verifier created by okta-auth-js earlier) which malicious browser extensions would not have access to.

NOTE: Okta's library okta-auth-js handles the exchange of the authorization code for tokens behind the secnes.

Sample POST to /token:

// https://dev-39907415.okta.com/oauth2/default/v1/token
client_id: 0oa2vli34jPlCb5ee5d7
redirect_uri: http://localhost:8080/authorization-code/callback
grant_type: authorization_code
code: OJ6P8kNgTX9SWCPZe_FzfgqlSY095_w3Bg5mHzeyC6U
code_verifier: 798293d431af02bdeba9d3128aa51193ae9cb2fe5c8

Sample response from Auth Server:

access_token: "eyJraWQiOiJUTmJhREFfbDBua3RWRHdaMi12RlNUNENEVTlQM182VnBuZ3FRWmVEM0hvIiwiYWxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULl9OLVViSjJ5VXZabFJicEh0azUtUDI0Vl9hODZkWGxBTUVtQUpoZ0xqUkkiLCJpc3MiOiJodHRwczovL2Rldi0zOTkwNzQxNS5va3RhLmNvbS9vYXV0aDIvZGVmYXVsdCIsImF1ZCI6ImFwaTovL2RlZmF1bHQiLCJpYXQiOjE2Mzc4NjQxMzQsImV4cCI6MTYzNzg2NzczNCwiY2lkIjoiMG9hMnZsaTM0alBsQ2I1ZWU1ZDciLCJ1aWQiOiIwMHUydmhvM2tmOWR2YnhPaDVkNyIsInNjcCI6WyJlbWFpbCIsInByb2ZpbGUiLCJvcGVuaWQiXSwic3ViIjoid29qbmFyNDhAZ2l0aHViLm9rdGFpZHAifQ.Q898B89mYAGsg29SG1XmZaAcpkrk1AQqvvgbl0rSWJt_7ZNxenz3tMcFPV_dUR6TCvLpzMFBV5pTlHOvGIrxB-5gC1bf0bTmA0Z9iriyxaTBI-bkpeRmTlrfdkhmHfchbYuodbQmFvqvom_zAYWEOrlaRb6mxAHDQukf7ciXVT8ywZ5Yrg8A_boFZ2llPx3pun9s-rSvYaKgaiG0pDQXojKAvaWX84V84c23s2CAZArhNizFuZ6rF_0ktPtqNAKe_sw6m14NrV2UOtNSRvcOIFf8Gz60kCxh5b_-7qhrZFM2guhMkzFzFsIp8wJjBV05Flgd1dxU464z12d15bwcLA"
expires_in: 3600
id_token: "eyJraWQiOiJUTmJhREFfbDBua3RWRHdaMi12RlNUNENEVTlQM182VnBuZ3FRWmVEM0hvIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHUydmhvM2tmOWR2YnhPaDVkNyIsIm5hbWUiOiJTenltb24gV29qbmFyIiwiZW1haWwiOiJzenltb25yd29qbmFyQGdtYWlsLmNvbSIsInZlciI6MSwiaXNzIjoiaHR0cHM6Ly9kZXYtMzk5MDc0MTUub2t0YS5jb20vb2F1dGgyL2RlZmF1bHQiLCJhdWQiOiIwb2EydmxpMzRqUGxDYjVlZTVkNyIsImlhdCI6MTYzNzg2NDEzNCwiZXhwIjoxNjM3ODY3NzM0LCJqdGkiOiJJRC42dnBFNGN6bVU4T2t4aDQyRDVLaldBVUhwNmczbGlrVVNyRlQ5RVBkdFBBIiwiYW1yIjpbInB3ZCJdLCJpZHAiOiIwMG8ydmhrcnMzQXVLU29ZNTVkNyIsIm5vbmNlIjoiSk00OFFSUE5UeGtGUDIweDRJUXJKeUM3M2wwZ0p2QnB4RUZSUWhkcXpkd3VaOEtmcXlDc2pBZVFrTjhycFdsaSIsInByZWZlcnJlZF91c2VybmFtZSI6Indvam5hcjQ4QGdpdGh1Yi5va3RhaWRwIiwiYXV0aF90aW1lIjoxNjM3ODYzOTMyLCJhdF9oYXNoIjoiaUNTclNMb3ZWU0xheVIzNk9aQUdRUSJ9.AVH-lKxgHYJGwDD0mvmiqZwxg9upgQx8im7qXD_JiFbHUS5PHL0d2p0DvDHLXFPXwF1-iK1I94lSXtA865chRDZSmkKfJNFV1Jj9g4dPYkQl0SB8FnKbZN3YVv-viiVuy9DquRV1kTIBWbv9C8fufGpupV8mg33WSrw1PvdfZP-MWN3zG_Og0whX6m4CXFGwIa5YjUGyiUfN9M4XK0fqMUVhjfoxN2SwJsZ142jQtBKnaVhXh3FPqCauYsyn_HCIeDgfoZ4WSlQZWjEPzdoHiD9uNfVgUb4F-Ip8e3LHjAfxj3vkyM5BCavQZOJlivQ0lC8WMZBQM2IKcdNndKRbYg"
scope: "email profile openid"
token_type: "Bearer"

NOTE: Unlike in the Implicit Flow, the request is a POST and the response comes back on the same channel. It is not a redirect like in the Implicit Flow so it will not be in your browser's history which is why the PKCE approach is much more secure than the Implicit Flow.

Docker Notes

Using Docker CLI

// obj --> container, image, network or volume
docker <obj> <command> <options>


// To run the hello-world image
docker run hello-world

Publishing Ports

--publish <host_port>:<container_port>

Keep Running in Background

docker run --detached --publish 8080:80 hello-world

Useful Commands

// List running containers
docker container ls

// List running and stopped containers
docker container ls --all

// Run with custom name
docker container run --name hello-world-c hello-world

// Rename container (stopped or running)
docker container rename <container identifier> <new name>

// Gracefully stop (Sends SIGTERM then SIGKILL if not responding in given time)
docker container stop <container_id>

// Start a stopped/killed container
// In detached mode by default and retains all previous port configs
docker container start <container_id>

// Restart a running container
docker container restart <container_id>

// Create container without running it
docker container create --publish 8080:80 <image_id>

// Remove dangling container
docker container rm <container_id>

// Remove all dangling containers
docker container prune

Container Names

Every container can be identified by:

  • NAME --> two words separeted by an underscore
  • CONTAINER ID --> 64 random char string

Creating and Running Containers

The docker container run command is in reality:

  • docker container create --> create a container from given image
  • docker container start --> start an already created container

TIP: Running the start or run command with the --rm flag removes the container once it is stopped.

Running Interactive Containers

Images like ubuntu are configured to run a shell by default. Running them with the --it flag lets us run in interactive mode:

docker container --rm --it run ubuntu 

In reality it is: --interactive --> connect the input stream to the cotnainer so that we can send input to the container shell. --tty --> provides a native terminal-like terminal with formatting by creating a pseudo-tty.

TIP: The busybox image is a swiss army knife of unix utilities.

Running Command Inside Containers

When executing the run command, anything after the image name gets passed to the default image entry-point which is usually the terminal (sh) for non-executable images.

// To encode a string in base64 using busybox
docker container --rn run busybox echo -n <some_string_to_be_encoded> | base64

Bind Mounts

--volume or -v

We can use the -v option for both run and create commands.

Let's say we wanted to run an executable that removes files with a given extension from the current working directory. To do so we need to give the container access to the local file system. One way of doing so is with bind mounts.

NOTE: The local source direcoty and the container destination directory have to be specified with absolute paths.

// --volume <local file system directory absolute path>:<container file system directory absolute path>:<read write access>
docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf

Narrowing in Typescript

narrowing : compiler's process of refining the type to be more specific than declared through analyzing code execution flow. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.

TS narrows types in branches of execution guarded by type buards.

Constructs TS Understands for Narrowing

  • The typeof type guard
    • string
    • number
    • boolean
    • bigint
    • symbol
    • undefined
    • object
    • function

TS understands type guards using the typeof operator and can narrow types in different execution branches.

WARNING: typeof null returns "object" not the string "null"!

Truthiness Narrowing

In JS if statements and conditionals don't expect the condition to evaluate to type boolean. JS first "coerces" the condition to a boolean and then chooses execution paths based on whether it is true or false.

Values that evaluate to false:

  • 0
  • NaN
  • ""
  • 0n
  • null
  • undefined

We can always coerce a value to a boolean using Boolean or !! (infers a narrower literal boolean type).

WARNING: Watch out for checking truthiness of things like 0 or "".

Equality Narrowing

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // We can now call any 'string' method on 'x' or 'y'.
    // TS knows that since 'string' is the only common type that both x and y can share
    // by checking x === y the types must also be equal.
    x.toUpperCase();
    y.toLowerCase();
  } else {
    console.log(x);
    console.log(y);
  }
}

Terminal Notes

Commands

cat /etc/os-release uname -a

TS Notes

Qs

  • What is type inference?
  • What is contextual typing?
  • What is the use case for the optional chaining ? operator?
  • When should we use Enums in TS?
  • What is bigint used for?

dynamic typing : running the code to see what occurs. static typing : make predictions about what is expected before code runs. structural type system : only concerned with structure and type capabilities like TS.

TS looks at the different entities in our programs and performs static analysis to tell us when things might be awry.

For primitive values like string or number we can determine their type at runtime with the typeof operator. However, for other things like functions, there is no such runtime mechanism to check. We can only know what JS does with the below argument to fn is by calling fn.

REMEMBER: Type annotations never change the runtime behavior of our code!

Strictness

  • strict : the flag makes the compiler run in strictest mode.
  • noImplicitAny : the compiler will hrow an error whenever a variable is inferred to be of lenient type any.
  • strictNullChecks : frees us from the burden of remembering to check if a value is null or undefined (by default both can be assigned to any other type).

Everyday Types

any

  • If we don't explicitly annotate a type and TS can't infer a type from context, the default is any.
  • Disables all further type checking.
  • We are telling the compiler that we know better and don't need to toil to prove a value is of a given type.

Primitives

  • boolean
  • string
  • number

Object Type

function fn(pt: { x: number, y: number }) {
  //...
}

Union Type

A type composed of two or more other types which represents a value that might be any of those types.

  • To provide a value that satisfies a union type --> provide a type matching any of the union members.
  • TS will only allow operations on a union type if the action is permissible on all union members.

Type Alias

A name for any other type. It is equivalent to using the aliased type directly.

Type Assertion

Sometimes we might know more about a value than TS. For example, TS might only know that document.getElementById will return a HTMLElement but we might be certain it will always be an HTMLCanvasElement given our logic and the ID.

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

NOTE: As with type annotations, no run-time checks are performed if the assertion was fulfilled.

/**
  Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid. If this happens, you can
  use two assertions, first to any (or unknown, which we’ll introduce later), then to the desired type:
*/
const a = (expr as unknown) as TargetType;

Literal Types

Can be combined into unions to express values with a finite set of values.

Literal Interface

When initializing a variable with an object, TS assumes the properties of that object might change later. Which means that if a property is initialized to 0, later updating the property to 1 is allowed. The property has the more general type number and not 0 because types determine reading/writing behavior.

const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// --> Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

/**
  In the above example req.method is inferred to be string, not "GET". Because code can be evaluated between the creation of req and the call of
  `handleRequest` which could assign a new string like "GUESS" to req.method, TypeScript considers this code to have an error.

  There are 2 workarounds:
  1. You can change the inference by adding a type assertion in either location:
    // Change 1:
    const req = { url: "https://example.com", method: "GET" as "GET" };
    // Change 2
    handleRequest(req.url, req.method as "GET");

  2. You can use as const to convert the entire object to be type literals:
    const req = { url: "https://example.com", method: "GET" } as const;
    handleRequest(req.url, req.method);
  
    The as const suffix acts like const but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string or number.
*/

Enums

Unlike the other types, Enums have a run-time impact and result in additional JS being run.

  • (SW)TODO

Non-null undefined Operator !

We can assert a value is not null or undefined using the Postfix ! operator when we are certain a value is neither.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment