Add documentation about authorization and authentication
parent
e8cc3a1bfd
commit
0c97550e2c
110
docs/auth.md
110
docs/auth.md
|
@ -1,9 +1,111 @@
|
|||
Chronograf with OAuth 2.0 (Github-style)
|
||||
|
||||
Originally Authored with Hackmd.io Link
|
||||
## Chronograf with OAuth 2.0 (Github-style)
|
||||
|
||||
OAuth 2.0 Style Authentication
|
||||
|
||||
Assumptions: The user has created an "OAuth Application" on Github to authenticate against.
|
||||
|
||||
### Configuration
|
||||
|
||||
To use authentication in Chronograf, both Github OAuth and JWT signature need to be configured.
|
||||
|
||||
#### Creating Github OAuth Application
|
||||
|
||||
To create a Github OAuth Application follow the [Register your app](https://developer.github.com/guides/basics-of-authentication/#registering-your-app) instructions.
|
||||
Essentially, you'll register your application [here](https://github.com/settings/applications/new)
|
||||
|
||||
The `Homepage URL` should be Chronograf's full server name and port. If you are running it locally for example, make it `http://localhost:8888`
|
||||
|
||||
The `Authorization callback URL` must be your the location of the `Homepage URL` plus `/oauth/github/callback`. For example, if `Homepage URL` was
|
||||
`http://localhost:8888` then the `Authorization callback URL` should be `http://localhost:8888/oauth/github/callback`.
|
||||
|
||||
Github will provide a `Client ID` and `Client Secret`. To register these values with chronograf set the following environment variables:
|
||||
|
||||
* `GH_CLIENT_ID`
|
||||
* `GH_CLIENT_SECRET`
|
||||
|
||||
For example:
|
||||
|
||||
```sh
|
||||
export GH_CLIENT_ID=b339dd4fddd95abec9aa
|
||||
export GH_CLIENT_SECRET=260041897d3252c146ece6b46ba39bc1e54416dc
|
||||
```
|
||||
|
||||
#### Configuring JWT signature
|
||||
|
||||
Set a [JWT](https://tools.ietf.org/html/rfc7519) signature to a random string.
|
||||
*Keep this random string around!*
|
||||
|
||||
You'll need it each time you start a chronograf server because it is used to verify
|
||||
user authorization.
|
||||
|
||||
```sh
|
||||
export TOKEN_SECRET=supersupersecret
|
||||
```
|
||||
|
||||
### Design
|
||||
|
||||
The Chronograf authentication scheme is a standard [web application](https://developer.github.com/v3/oauth/#web-application-flow) OAuth flow.
|
||||
|
||||

|
||||
|
||||
The browser receives a cookie from Chronograf, authorizing it. The contents of the cookie is a JWT whose "sub" claim is the user's primary
|
||||
github email address.
|
||||
|
||||
On each request to Chronograf, the JWT contained in the cookie will be validated against the `TOKEN_SECRET` signature and checked for expiration.
|
||||
The JWT's "sub" becomes the [principal](https://en.wikipedia.org/wiki/Principal_(computer_security)) used for authorization to resources.
|
||||
|
||||
The API provides three endpoints `/oauth`, `/oauth/logout` and `/oauth/github/callback`.
|
||||
|
||||
#### /oauth
|
||||
|
||||
The `/oauth` endpoint redirects to Github for OAuth. Chronograf sets the OAuth `state` request parameter to a JWT with a random "sub". Using $TOKEN_SECRET `/oauth/github/callback`
|
||||
can validate the `state` parameter without needing `state` to be saved.
|
||||
|
||||
#### /oauth/github/callback
|
||||
|
||||
The `/oauth/github/callback` receives the OAuth `authorization code` and `state`.
|
||||
|
||||
First, it will validate the `state` JWT from the `/oauth` endpoint. `JWT` validation
|
||||
only requires access to the signature token. Therefore, there is no need for `state`
|
||||
to be saved. Additionally, multiple Chronograf servers will not need to share 3rd
|
||||
-party storage to synchronize `state`. If thiis validation fails, the request
|
||||
will be redirected to `/login`.
|
||||
|
||||
Secondly, the endpoint will use the `authorization code` to retrieve a valid OAuth token
|
||||
with the `user:email` scope. If unable to get a token from Github, the request will
|
||||
be redirected to `/login`.
|
||||
|
||||
Finally, the endpoint will attempt to get the primary email address of the Github user.
|
||||
Again, if not successfully, the request will redirect to `/login`.
|
||||
|
||||
The email address is used as the subject claim for a new JWT. This JWT becomes the
|
||||
value of the cookie sent back to the browser. The cookie is valid for thirty days.
|
||||
|
||||
Next, the request is redirected to `/`.
|
||||
|
||||
All API calls to `/chronograf/v1` check if the cookie exists and its JWT is valid.
|
||||
If the JWT is invalid during the API call, the API returns `HTTP/1.1 401 Unauthorized`.
|
||||
|
||||
#### /oauth/logout
|
||||
|
||||
Simply, expires the session cookie and redirects to `/`.
|
||||
|
||||
### Authorization
|
||||
|
||||
After successful validation of the JWT, each API endpoint of `/chronograf/v1` receives the
|
||||
JWT subject within the `http.Request` as a `context.Context` value.
|
||||
|
||||
Within the Go API code all interfaces take `context.Context`. This means that each
|
||||
interface can use value as a principal. The design allows for authorization to happen
|
||||
at the level of design most closely related to the problem.
|
||||
|
||||
An Go usage example could be:
|
||||
|
||||
```go
|
||||
func ShallIPass(ctx context.Context) (string, error) {
|
||||
principal := ctx.Value(mrfusion.PrincipalKey).(mrfusion.Principal)
|
||||
if principal != "gandolf@moria.misty.mt" {
|
||||
return "you shall not pass", mrfusion.ErrAuthentication
|
||||
}
|
||||
return "run you fools", nil
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
### Handler Stack
|
||||
|
||||
Entry (JWT Check) ---> No JWT -> Login Page --> redirect to OAuth provider --> redirect to /token --> return JWT token --> back to Entry
|
||||
| ^
|
||||
| |
|
||||
| |
|
||||
| bad
|
||||
| |
|
||||
v |
|
||||
JWT --> Check token ---> good --> Return resource or asset
|
||||
|
||||
|
||||
### Authorized
|
||||
Once the user has been authenticated, the github email address is sent via the context.Context to
|
||||
the follow-on requests. The value is keyed with mrfusion.PrincipalKey
|
||||
|
||||
To get:
|
||||
|
||||
```go
|
||||
principal := ctxt.Value(mrfusion.PrincipalKey).(mrfusion.Principal)
|
||||
```
|
||||
|
||||
|
||||
|
||||
Entry (No Auth) --> / --> redirect because no auth --> /login (index.html) --> render login button --> click --> /oauth/github --> redirect to GH --> redirects /oauth/github/callback --> redirects to "/" or on failure "/login"
|
||||
|
||||
Entry (auth) --> / (index.html)
|
||||
Only routes protected are those in /chronograf/v1
|
Loading…
Reference in New Issue