Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: adhocore/goic
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.0.9
Choose a base ref
...
head repository: adhocore/goic
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Oct 14, 2021

  1. docs: add yahoo docs

    adhocore committed Oct 14, 2021
    Copy the full SHA
    af8126f View commit details
  2. Copy the full SHA
    1bdb0cd View commit details
  3. Copy the full SHA
    27b2267 View commit details
  4. Copy the full SHA
    cfec49a View commit details
  5. Copy the full SHA
    a8ed467 View commit details
  6. chore: use printf

    adhocore committed Oct 14, 2021
    Copy the full SHA
    0cb25d8 View commit details
  7. Copy the full SHA
    26ac14e View commit details
  8. Merge pull request #9 from adhocore/2-refresh_token

    Support refresh token, update docs
    adhocore authored Oct 14, 2021
    Copy the full SHA
    bb0a05a View commit details
  9. Copy the full SHA
    87e204f View commit details
  10. docs: update

    adhocore authored Oct 14, 2021
    Copy the full SHA
    f4b2b18 View commit details

Commits on Oct 15, 2021

  1. docs: update todo

    adhocore authored Oct 15, 2021
    Copy the full SHA
    a3d6e4a View commit details
  2. Copy the full SHA
    b1783b4 View commit details
  3. docs: for signout

    adhocore committed Oct 15, 2021
    Copy the full SHA
    8a55865 View commit details
  4. Copy the full SHA
    25dde2a View commit details
  5. chore: signout example

    adhocore committed Oct 15, 2021
    Copy the full SHA
    c7ee172 View commit details
  6. Merge pull request #10 from adhocore/5-signout

    Implement signout
    adhocore authored Oct 15, 2021
    Copy the full SHA
    108b1bf View commit details
  7. Copy the full SHA
    d23d899 View commit details
  8. docs: cleanup misinfo

    adhocore authored Oct 15, 2021
    Copy the full SHA
    1fc483b View commit details

Commits on Oct 16, 2021

  1. Copy the full SHA
    545c012 View commit details
  2. Copy the full SHA
    c78812c View commit details
  3. Copy the full SHA
    da50d61 View commit details
  4. feat: add RevokeToken

    adhocore committed Oct 16, 2021
    Copy the full SHA
    b8f3678 View commit details
  5. Copy the full SHA
    b7c04c5 View commit details
  6. Copy the full SHA
    9cf00c0 View commit details
  7. docs: code clarity

    adhocore committed Oct 16, 2021
    Copy the full SHA
    066e7c2 View commit details
  8. docs: func doc

    adhocore committed Oct 16, 2021
    Copy the full SHA
    6b9e0b0 View commit details
  9. Merge pull request #11 from adhocore/6-revoke-token

    Implement revoke token
    adhocore authored Oct 16, 2021
    Copy the full SHA
    4966846 View commit details
  10. Copy the full SHA
    070f1d3 View commit details
  11. Copy the full SHA
    bf214e0 View commit details
  12. chore: minor update

    adhocore authored Oct 16, 2021
    Copy the full SHA
    a3ae5aa View commit details

Commits on Oct 26, 2021

  1. Copy the full SHA
    3ae7b56 View commit details
  2. feat: add GetProvider()

    adhocore committed Oct 26, 2021
    Copy the full SHA
    c3a960e View commit details
  3. Copy the full SHA
    d03d9f8 View commit details

Commits on Nov 3, 2021

  1. feat: add paypal support

    adhocore committed Nov 3, 2021
    Copy the full SHA
    1d11cb4 View commit details
  2. feat: add paypal support

    adhocore committed Nov 3, 2021
    Copy the full SHA
    e8f2a62 View commit details
  3. Merge pull request #14 from adhocore/paypal

    feat: add paypal support
    adhocore authored Nov 3, 2021
    Copy the full SHA
    8825582 View commit details
  4. Copy the full SHA
    22a3f7c View commit details
  5. Merge pull request #15 from adhocore/paypal

    feat: add paypal support
    adhocore authored Nov 3, 2021
    Copy the full SHA
    f69a3ad View commit details
  6. Copy the full SHA
    805f845 View commit details

Commits on Sep 26, 2022

  1. chore: ~ [skip ci][ci skip]

    adhocore authored Sep 26, 2022
    Copy the full SHA
    3b6bf98 View commit details

Commits on Nov 1, 2022

  1. Copy the full SHA
    6351be2 View commit details

Commits on Mar 25, 2023

  1. docs: [skip ci]

    adhocore authored Mar 25, 2023
    Copy the full SHA
    c884124 View commit details

Commits on Aug 11, 2023

  1. chore: bump go to 1.18

    adhocore committed Aug 11, 2023
    Copy the full SHA
    4d398a7 View commit details
  2. Copy the full SHA
    85c0613 View commit details
  3. Copy the full SHA
    28e6c03 View commit details
  4. Copy the full SHA
    0d99d8f View commit details
  5. Copy the full SHA
    ee14f97 View commit details
  6. Copy the full SHA
    989b2e5 View commit details
  7. Copy the full SHA
    9b60851 View commit details
  8. docs: update

    adhocore committed Aug 11, 2023
    Copy the full SHA
    f1422dc View commit details
Showing with 648 additions and 122 deletions.
  1. +2 −0 .github/FUNDING.yml
  2. +73 −0 CHANGELOG.md
  3. +140 −18 README.md
  4. +1 −1 VERSION
  5. +33 −3 examples/all.go
  6. +31 −0 examples/facebook.go
  7. +2 −2 go.mod
  8. +2 −2 go.sum
  9. +232 −71 goic.go
  10. +95 −2 provider.go
  11. +36 −23 user.go
  12. +1 −0 util.go
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github: adhocore
custom: ['https://paypal.me/ji10']
73 changes: 73 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,76 @@
## [v0.0.15](https://github.com/adhocore/goic/releases/tag/v0.0.15) (2021-11-03)

### Features
- Add paypal support (Jitendra Adhikari) [_e8f2a62_](https://github.com/adhocore/goic/commit/e8f2a62)
- Add paypal support (Jitendra Adhikari) [_1d11cb4_](https://github.com/adhocore/goic/commit/1d11cb4)


## [v0.0.14](https://github.com/adhocore/goic/releases/tag/v0.0.14) (2021-10-26)

### Features
- Add GetProvider() (Jitendra Adhikari) [_c3a960e_](https://github.com/adhocore/goic/commit/c3a960e)
- Add AuthRedirectURL() for the case where net/http is not used (Jitendra Adhikari) [_3ae7b56_](https://github.com/adhocore/goic/commit/3ae7b56)

### Miscellaneous
- Minor update (Jitendra) [_a3ae5aa_](https://github.com/adhocore/goic/commit/a3ae5aa)


## [v0.0.12](https://github.com/adhocore/goic/releases/tag/v0.0.12) (2021-10-16)

### Features
- Add RevokeToken (Jitendra Adhikari) [_b8f3678_](https://github.com/adhocore/goic/commit/b8f3678)
- Add helper funcs to check abilities and create auth header (Jitendra Adhikari) [_545c012_](https://github.com/adhocore/goic/commit/545c012)

### Bug Fixes
- Token is not mandatory for signout (Jitendra Adhikari) [_da50d61_](https://github.com/adhocore/goic/commit/da50d61)
- Some provider like yahoo uses key token_revocation_endpoint (Jitendra Adhikari) [_c78812c_](https://github.com/adhocore/goic/commit/c78812c)

### Miscellaneous
- Add revoke token example (Jitendra Adhikari) [_b7c04c5_](https://github.com/adhocore/goic/commit/b7c04c5)

### Documentations
- Ability support matrix (Jitendra) [_070f1d3_](https://github.com/adhocore/goic/commit/070f1d3)
- Func doc (Jitendra Adhikari) [_6b9e0b0_](https://github.com/adhocore/goic/commit/6b9e0b0)
- Code clarity (Jitendra Adhikari) [_066e7c2_](https://github.com/adhocore/goic/commit/066e7c2)
- Add revocation section and code snippet (Jitendra Adhikari) [_9cf00c0_](https://github.com/adhocore/goic/commit/9cf00c0)
- Cleanup misinfo (Jitendra) [_1fc483b_](https://github.com/adhocore/goic/commit/1fc483b)


## [v0.0.11](https://github.com/adhocore/goic/releases/tag/v0.0.11) (2021-10-15)

### Features
- Add signout method, it needs to be called programattically (Jitendra Adhikari) [_b1783b4_](https://github.com/adhocore/goic/commit/b1783b4)

### Bug Fixes
- Provider may not even suppport signout (Jitendra Adhikari) [_25dde2a_](https://github.com/adhocore/goic/commit/25dde2a)

### Miscellaneous
- Signout example (Jitendra Adhikari) [_c7ee172_](https://github.com/adhocore/goic/commit/c7ee172)

### Documentations
- For signout (Jitendra Adhikari) [_8a55865_](https://github.com/adhocore/goic/commit/8a55865)
- Update todo (Jitendra) [_a3d6e4a_](https://github.com/adhocore/goic/commit/a3d6e4a)
- Update (Jitendra) [_f4b2b18_](https://github.com/adhocore/goic/commit/f4b2b18)


## [v0.0.10](https://github.com/adhocore/goic/releases/tag/v0.0.10) (2021-10-14)

### Features
- Support refresh_token grant (Jitendra Adhikari) [_27b2267_](https://github.com/adhocore/goic/commit/27b2267)

### Internal Refactors
- Rename func arg, fix auth code grant (Jitendra Adhikari) [_a8ed467_](https://github.com/adhocore/goic/commit/a8ed467)
- Make RequestAuth able to be used standalone from outside (Jitendra Adhikari) [_cfec49a_](https://github.com/adhocore/goic/commit/cfec49a)

### Miscellaneous
- Use printf (Jitendra Adhikari) [_0cb25d8_](https://github.com/adhocore/goic/commit/0cb25d8)
- Update docs, cleanup unused (Jitendra Adhikari) [_1bdb0cd_](https://github.com/adhocore/goic/commit/1bdb0cd)

### Documentations
- Add detailed API docs for standalone/manual usage (Jitendra Adhikari) [_26ac14e_](https://github.com/adhocore/goic/commit/26ac14e)
- Add yahoo docs (Jitendra Adhikari) [_af8126f_](https://github.com/adhocore/goic/commit/af8126f)


## [v0.0.9](https://github.com/adhocore/goic/releases/tag/v0.0.9) (2021-10-14)

### Features
158 changes: 140 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
# adhocore/goic

[![Latest Version](https://img.shields.io/github/release/adhocore/gronx.svg?style=flat-square)](https://github.com/adhocore/goic/releases)
[![Latest Version](https://img.shields.io/github/release/adhocore/goic.svg?style=flat-square)](https://github.com/adhocore/goic/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Go Report](https://goreportcard.com/badge/github.com/adhocore/goic)](https://goreportcard.com/report/github.com/adhocore/goic)
[![Donate 15](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+15)](https://www.paypal.me/ji10/15usd)
[![Donate 25](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+25)](https://www.paypal.me/ji10/25usd)
[![Donate 50](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+50)](https://www.paypal.me/ji10/50usd)
[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Simple+Golang+OpenID+Connect+client&url=https://github.com/adhocore/goic&hashtags=go,golang,openid,oauth,openid-connect,connect,oauth2)
[![Support](https://img.shields.io/static/v1?label=Support&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/adhocore)
<!-- [![Donate 15](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+15)](https://www.paypal.me/ji10/15usd)
[![Donate 25](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+25)](https://www.paypal.me/ji10/25usd)
[![Donate 50](https://img.shields.io/badge/donate-paypal-blue.svg?style=flat-square&label=donate+50)](https://www.paypal.me/ji10/50usd) -->


GOIC, **Go Open ID Connect**, is OpenID connect client library for Golang.
It supports the *Authorization Code Flow* of OpenID Connect specification.
It doesn't yet support `refresh_token` grant type and that will be added later.

It is a weekend hack project and is work in progress and not production ready yet.

# Installation

```sh
go get github.com/adhocore/goic
go get -u github.com/adhocore/goic
```

# Usage
@@ -27,12 +26,15 @@ Let's say `/auth/o8`. Then the provider name follows it.
All the OpenID providers that your server should support will need a unique name and each of the
providers get a URI like so `/auth/o8/<name>`. Example:

| Provider | Name | OpenID URI |
|----------|------|------------|
| Google | google | `/auth/o8/google` |
| Microsoft | microsoft | `/auth/o8/microsoft` |
| Provider | Name | OpenID URI | Revocation | Signout |
|----------|------|------------|----------|------------|
| Google | google | `/auth/o8/google` | Yes | No
| Facebook | facebook | `/auth/o8/facebook` | No | No
| Microsoft | microsoft | `/auth/o8/microsoft` | No | Yes
| Yahoo | yahoo | `/auth/o8/yahoo` | Yes | No
| Paypal | paypal | `/auth/o8/paypal` | No | No

> All the providers **must** provide .well-known configurations for OpenID auto discovery.
**Important:** All the providers **must** provide .well-known configurations for OpenID auto discovery.

Get ready with OpenID provider credentials (client id and secret).
For Google, check [this](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid).
@@ -41,7 +43,7 @@ To use the example below you need to export `GOOGLE_CLIENT_ID` and `GOOGLE_CLIEN
You also need to configure application domain and redirect URI in the Provider console/dashboard.
(redirect URI is same as OpenID URI in above table).

Below is an example code but instead of copy/pasting it entirely you can use it for reference.
Below is an example for authorization code flow but instead of copy/pasting it entirely you can use it for reference.

```go
package main
@@ -131,6 +133,7 @@ For example:
```html
<a href="https://localhost/auth/o8/google">Sign in with Google</a>
<a href="https://localhost/auth/o8/microsoft">Sign in with Microsoft</a>
<a href="https://localhost/auth/o8/yahoo">Sign in with Yahoo</a>
```

The complete flow is managed and handled by GOIC for you and on successful verification,
@@ -142,6 +145,124 @@ when GOIC has new features.

> The example and discussion here assume `localhost` domain so adjust that accordingly for your domains.
### Signing out

For signing out you need to manually invoke `g.SignOut()` from within http context. See the [API](#signout) below.
There is also a working [example](./examples/all.go). Note that not all Providers support signing out.

### Revocation

To revoke a token manually, invoke `g.RevokeToken()` from any context. See the [API](#revoketoken) below.
There is also a working [example](./examples/all.go). Note that not all Providers support revocation.

---
## GOIC API

GOIC supports full end-to-end for Authorization Code Flow, however if you want to manually interact, here's summary of API:

#### Supports

Use it to check if a provider is supported.

```go
g := goic.New("/auth/o8", false)
g.NewProvider("abc", "...").WithCredential("...", "...")

g.Supports("abc") // true
g.Supports("xyz") // false
```

#### RequestAuth

Manually request authentication from OpenID Provider. Must be called from within http context.

```go
g := goic.New("/auth/o8", false)
p := g.NewProvider("abc", "...").WithCredential("...", "...")

// Generate random unique state and nonce
state, nonce := goic.RandomString(24), goic.RandomString(24)
// You must save them to cookie/session, so it can be retrieved later for crosscheck

// redir is the redirect url in your host for provider of interest
redir := "https://localhost/auth/o8/" + p.Name

// Redirects to provider first and then back to above redir url
// res = http.ResponseWriter, req = *http.Request
err := g.RequestAuth(p, state, nonce, redir, res, req)
```

#### Authenticate

Manually attempt to authenticate after the request comes back from OpenID Provider.

```go
g := goic.New("/auth/o8", false)
p := g.NewProvider("abc", "...").WithCredential("...", "...")

// Read openid provider code from query param, and nonce from cookie/session etc
// PS: Validate that the nonce is relevant to the state sent by openid provider
code, nonce := "", ""

// redir is the redirect url in your host for provider of interest
redir := "https://localhost/auth/o8/" + p.Name

tok, err := g.Authenticate(p, code, nonce, redir)
```

#### RefreshToken

Use it to request Access token by using refresh token.

```go
g := goic.New("/auth/o8", false)
// ... add providers
old := &goic.Token{RefreshToken: "your refresh token", Provider: goic.Microsoft.Name}
tok, err := g.RefreshToken(old)
// Do something with new tok.AccessToken
```

#### Userinfo

Manually request Userinfo by using the token returned by Authentication above.

```go
g := goic.New("/auth/o8", false)
p := g.NewProvider("abc", "...").WithCredential("...", "...")
// ...
tok, err := g.Authenticate(p, code, nonce, redir)
user := g.UserInfo(tok)
err := user.Error
```

#### SignOut

Use it to sign out the user from OpenID Provider. Must be called from within http context.
Ideally, you would clear the session and logout user from your own system first and then invoke SignOut.

```go
g := goic.New("/auth/o8", false)
p := g.NewProvider("abc", "...").WithCredential("...", "...")
// ...
tok := &goic.Token{AccessToken: "current session token", Provider: p.Name}
err := g.SignOut(tok, "http://some/preconfigured/redir/uri", res, req)
// redir uri is optional
err := g.SignOut(tok, "", res, req)
```

#### RevokeToken

Use it to revoke the token so that is incapacitated.

```go
g := goic.New("/auth/o8", false)
p := g.NewProvider("abc", "...").WithCredential("...", "...")
// ...
tok := &goic.Token{AccessToken: "current session token", Provider: p.Name}
err := g.RevokeToken(tok)
```

---
### Demo

`GOIC` has been implemented in opensource project [adhocore/urlsh](https://github.com/adhocore/urlsh):
@@ -157,10 +278,10 @@ On successful verification your information is [echoed back](https://github.com/
---
# TODO

- Support refresh token grant_type
- Tests and more tests
- Release stable version
- ~~Support OpenID `Implicit Flow`~~ [Check #3](https://github.com/adhocore/goic/issues/3)
- [X] ~~Support refresh token grant_type~~ [Check #2](https://github.com/adhocore/goic/issues/2)
- [ ] Tests and more tests
- [ ] Release stable version
- [x] ~~Support OpenID `Implicit Flow`~~ [Check #3](https://github.com/adhocore/goic/issues/3)

## License

@@ -177,3 +298,4 @@ My other golang projects you might find interesting and useful:
- [**gronx**](https://github.com/adhocore/gronx) - Lightweight, fast and dependency-free Cron expression parser (due checker), task scheduler and/or daemon for Golang (tested on v1.13 and above) and standalone usage.
- [**urlsh**](https://github.com/adhocore/urlsh) - URL shortener and bookmarker service with UI, API, Cache, Hits Counter and forwarder using postgres and redis in backend, bulma in frontend; has [web](https://urlssh.xyz) and cli client
- [**fast**](https://github.com/adhocore/fast) - Check your internet speed with ease and comfort right from the terminal
- [**chin**](https://github.com/adhocore/chin) - A GO lang command line tool to show a spinner as user waits for some long running jobs to finish.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.0.9
v0.0.15
36 changes: 33 additions & 3 deletions examples/all.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"log"
"net/http"
"os"
@@ -13,10 +14,18 @@ func main() {
g.AddProvider(goic.Google.WithCredential(os.Getenv("GOOGLE_CLIENT_ID"), os.Getenv("GOOGLE_CLIENT_SECRET")))
g.AddProvider(goic.Microsoft.WithCredential(os.Getenv("MICROSOFT_CLIENT_ID"), os.Getenv("MICROSOFT_CLIENT_SECRET")))
g.AddProvider(goic.Yahoo.WithCredential(os.Getenv("YAHOO_CLIENT_ID"), os.Getenv("YAHOO_CLIENT_SECRET")))
g.AddProvider(goic.Paypal.WithCredential(os.Getenv("PAYPAL_CLIENT_ID"), os.Getenv("PAYPAL_CLIENT_SECRET")))
g.AddProvider(goic.Facebook.WithCredential(os.Getenv("FACEBOOK_CLIENT_ID"), os.Getenv("FACEBOOK_CLIENT_SECRET")))

g.UserCallback(func(t *goic.Token, u *goic.User, w http.ResponseWriter, r *http.Request) {
log.Println("token: ", t, "\nuser: ", u)
_, _ = w.Write([]byte("All good, check backend console"))
log.Printf("token: %v\nuser: %v\n", t, u)
uri1 := "https://localhost/auth/signout?p=" + t.Provider + "&t=" + t.AccessToken
uri1 = fmt.Sprintf(`,<br><br>click <a href="%s" target="_blank">here</a> to signout (some provider may not support it)`, uri1)
uri2 := "https://localhost/auth/revoke?p=" + t.Provider + "&t=" + t.AccessToken
uri2 = fmt.Sprintf(`,<br><br>click <a href="%s" target="_blank">here</a> to revoke (some provider may not support it)`, uri2)

w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte("All good, check backend console" + uri1 + uri2))
})

addr := "localhost:443"
@@ -25,9 +34,30 @@ func main() {
}

log.Println("Server running on https://localhost, You can visit any of the below URLs:")
for _, v := range []string{"google", "microsoft", "yahoo"} {
for _, v := range []string{"google", "facebook", "microsoft", "yahoo", "paypal"} {
log.Printf(" https://localhost/auth/o8/%s\n", v)
}
http.HandleFunc("/", g.MiddlewareFunc(handler))

// SignOut handler
http.HandleFunc("/auth/signout/", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
tok := &goic.Token{Provider: q.Get("p"), AccessToken: q.Get("t")}
if err := g.SignOut(tok, "", w, r); err != nil {
http.Error(w, "can't signout: "+err.Error(), http.StatusInternalServerError)
}
})

// Revoke Handler
http.HandleFunc("/auth/revoke/", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
tok := &goic.Token{Provider: q.Get("p"), AccessToken: q.Get("t")}
if err := g.RevokeToken(tok); err != nil {
http.Error(w, "Can't revoke: "+err.Error(), http.StatusInternalServerError)
return
}
_, _ = w.Write([]byte("Revoked token successfully"))
})

log.Fatal(http.ListenAndServeTLS(addr, "server.crt", "server.key", nil))
}
31 changes: 31 additions & 0 deletions examples/facebook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"log"
"net/http"

"github.com/adhocore/goic"
)

func main() {
g := goic.New("/auth/o8", true)

g.AddProvider(goic.Facebook.WithCredential("3462809713976120", "f6eab7509f137f45ff73d2fddf28604a"))

g.UserCallback(func(t *goic.Token, u *goic.User, w http.ResponseWriter, r *http.Request) {
log.Printf("token: %#v\n", t)
log.Printf("user: %#v\n", u)
_, _ = w.Write([]byte("All good, check backend console"))
})

handler := func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(r.Method + " " + r.URL.Path))
}

log.Println("Server running on https://localhost")
log.Println(" Visit https://localhost/auth/o8/facebook")

addr := "localhost:443"
http.HandleFunc("/", g.MiddlewareFunc(handler))
log.Fatal(http.ListenAndServeTLS(addr, "server.crt", "server.key", nil))
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/adhocore/goic

go 1.15
go 1.18

require github.com/golang-jwt/jwt/v4 v4.1.0
require github.com/golang-jwt/jwt/v5 v5.2.2
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
303 changes: 232 additions & 71 deletions goic.go

Large diffs are not rendered by default.

97 changes: 95 additions & 2 deletions provider.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package goic

import (
"encoding/base64"
"encoding/json"
"log"
"net/http"
"net/url"
"strings"
)

// Provider represents OpenID Connect provider
type Provider struct {
wellKnown *WellKnown
QueryFn func() string
err error
Name string
URL string
Scope string
host string
clientID string
clientSecret string
wellKnown *WellKnown
ResType string
Sandbox bool
discovered bool
}

// WellKnown represents OpenID Connect well-known config
@@ -25,6 +32,9 @@ type WellKnown struct {
AuthURI string `json:"authorization_endpoint"`
TokenURI string `json:"token_endpoint"`
UserInfoURI string `json:"userinfo_endpoint"`
SignOutURI string `json:"end_session_endpoint,omitempty"`
RevokeURI string `json:"revocation_endpoint,omitempty"`
XRevokeURI string `json:"token_revocation_endpoint,omitempty"`
AlgoSupport []string `json:"id_token_signing_alg_values_supported"`
jwks struct {
Keys []struct {
@@ -55,12 +65,36 @@ var Google = &Provider{
Scope: "openid email profile",
}

// Yahoo provider
var Yahoo = &Provider{
Name: "yahoo",
URL: "https://login.yahoo.com",
Scope: "openid openid2 email profile",
}

// Paypal live provider
var Paypal = &Provider{
Name: "paypal",
URL: "https://www.paypalobjects.com",
Scope: "openid email profile",
}

// PaypalSandbox provider
var PaypalSandbox = &Provider{
Name: "paypal",
Sandbox: true,
URL: "https://www.paypalobjects.com",
Scope: "openid email profile",
}

var Facebook = &Provider{
Name: "facebook",
ResType: "code",
URL: "https://www.facebook.com",
Scope: "openid email public_profile",
wellKnown: &WellKnown{TokenURI: "https://graph.facebook.com/v17.0/oauth/access_token"},
}

// WithCredential sets client id and secret for a Provider
func (p *Provider) WithCredential(id, secret string) *Provider {
if id == "" || secret == "" {
@@ -83,9 +117,23 @@ func (p *Provider) WithScope(s string) *Provider {
return p
}

// SetQuery sets query func for inital auth request
func (p *Provider) SetQuery(fn func() string) *Provider {
p.QueryFn = fn
return p
}

// SetErr sets last encountered error
func (p *Provider) SetErr(err error) { p.err = err }

// Is checks if provider is given type
func (p *Provider) Is(name string) bool {
return p.Name == name
}

// getWellKnown gets the well known config from Provider remote
func (p *Provider) getWellKnown() (*WellKnown, error) {
if nil != p.wellKnown {
if nil != p.wellKnown && p.discovered {
return p.wellKnown, nil
}

@@ -96,10 +144,15 @@ func (p *Provider) getWellKnown() (*WellKnown, error) {
}
defer res.Body.Close()

p.discovered = true
if err := json.NewDecoder(res.Body).Decode(&p.wellKnown); err != nil {
return nil, err
}

if p.wellKnown.RevokeURI == "" && p.wellKnown.XRevokeURI != "" {
p.wellKnown.RevokeURI = p.wellKnown.XRevokeURI
}

if p.wellKnown.KeysURI == "" {
return p.wellKnown, nil
}
@@ -117,3 +170,43 @@ func (p *Provider) getWellKnown() (*WellKnown, error) {

return p.wellKnown, nil
}

// GetURI gets an endpoint for given action
func (p *Provider) GetURI(action string) (uri string) {
switch action {
case "auth":
uri = p.wellKnown.AuthURI
case "token":
uri = p.wellKnown.TokenURI
case "userinfo":
uri = p.wellKnown.UserInfoURI
case "revoke":
uri = p.wellKnown.RevokeURI
case "signout":
uri = p.wellKnown.SignOutURI
}

// if p.Sandbox && p.Is("paypal") {
// uri = strings.Replace(uri, ".paypal.com", ".sandbox.paypal.com", 1)
// }
return uri
}

// CanRevoke checks if token can be revoked for this Provider
func (p *Provider) CanRevoke() bool {
return p.wellKnown.RevokeURI != ""
}

// CanSignOut checks if token can be signed out for this Provider
func (p *Provider) CanSignOut() bool {
return p.wellKnown.SignOutURI != ""
}

// AuthBasicHeader gives a string ready to use as Authorization header
// The returned value contains "Basic " prefix already
func (p *Provider) AuthBasicHeader() string {
id := url.PathEscape(url.QueryEscape(p.clientID))
pass := url.PathEscape(url.QueryEscape(p.clientSecret))

return "Basic " + base64.StdEncoding.EncodeToString([]byte(id+":"+pass))
}
59 changes: 36 additions & 23 deletions user.go
Original file line number Diff line number Diff line change
@@ -5,31 +5,20 @@ import (
"encoding/json"
"strings"

"github.com/golang-jwt/jwt/v4"
"github.com/golang-jwt/jwt/v5"
)

// User represents user from well know user info endpoint
// User represents user from well known user info endpoint
type User struct {
Error error `json:"-"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified,omitempty"`
FamilyName string `json:"family_name,omitempty"`
GivenName string `json:"given_name,omitempty"`
Locale string `json:"locale,omitempty"`
Name string `json:"name"`
Picture string `json:"picture,omitempty"`
Subject string `json:"sub,omitempty"`
Error error `json:"-"`
}

// Token represents token structure from well known token endpoint
type Token struct {
Err string `json:"error,omitempty"`
ErrDesc string `json:"error_description,omitempty"`
AuthURI string `json:"authorization_endpoint,omitempty"`
IDToken string `json:"id_token"`
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Provider string `json:"provider,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
}

// withError embeds Error to User
@@ -38,27 +27,51 @@ func (u *User) withError(err error) *User {
return u
}

func verifyClaims(tok *Token, nonce, aud string) (jwt.Claims, error) {
func (u *User) FromClaims(c jwt.MapClaims) *User {
u.Name = c["name"].(string)
u.GivenName = c["given_name"].(string)
u.FamilyName = c["family_name"].(string)
u.Email = c["email"].(string)
u.Picture = c["picture"].(string)
u.Subject = c["sub"].(string)
return u
}

// Token represents token structure from well known token endpoint
type Token struct {
Claims jwt.MapClaims `json:"-"`
Err string `json:"error,omitempty"`
ErrDesc string `json:"error_description,omitempty"`
IDToken string `json:"id_token"`
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Provider string `json:"provider,omitempty"`
}

// verifyClaims verifies the claims of a Token
func (tok *Token) VerifyClaims(nonce, aud string) (err error) {
claims := jwt.MapClaims{}
tok.Claims = jwt.MapClaims{}

seg := strings.Split(tok.IDToken, ".")
if len(seg) != 3 {
return claims, ErrTokenInvalid
return ErrTokenInvalid
}

buf, _ := Base64UrlDecode(seg[1])
if err := json.Unmarshal(buf, &claims); err != nil {
return claims, ErrTokenClaims
return ErrTokenClaims
}

usrNonce, ok := claims["nonce"]
if ok && subtle.ConstantTimeCompare([]byte(nonce), []byte(usrNonce.(string))) == 0 {
return claims, ErrTokenNonce
return ErrTokenNonce
}

_, ok = claims["aud"]
if ok && !claims.VerifyAudience(aud, true) {
return claims, ErrTokenAud
if err = jwt.NewValidator().Validate(claims); err != nil {
return err
}

return claims, nil
tok.Claims = claims // attach only if valid
return nil
}
1 change: 1 addition & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -57,6 +57,7 @@ func ParseExponent(es string) int {
return int(new(big.Int).SetBytes(buf).Uint64())
}

// GetCurve gets the elliptic.Curve from last 3 chars of string s
func GetCurve(s string) elliptic.Curve {
s3 := s[len(s)-3:]
if s3 == "256" {