Full Code of dwyl/elixir-auth-google for AI

main 5d1ce87ebe3c cached
16 files
60.9 KB
16.7k tokens
25 symbols
1 requests
Download .txt
Repository: dwyl/elixir-auth-google
Branch: main
Commit: 5d1ce87ebe3c
Files: 16
Total size: 60.9 KB

Directory structure:
gitextract_jwgh7fao/

├── .env_sample
├── .formatter.exs
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── config/
│   ├── config.exs
│   └── test.exs
├── create-google-app-guide.md
├── lib/
│   ├── elixir_auth_google.ex
│   └── httpoison_mock.ex
├── mix.exs
└── test/
    ├── elixir_auth_google_test.exs
    └── test_helper.exs

================================================
FILE CONTENTS
================================================

================================================
FILE: .env_sample
================================================
export GOOGLE_CLIENT_ID=YourAppsClientId.apps.googleusercontent.com
export GOOGLE_CLIENT_SECRET=SuperSecret
# export GOOGLE_SCOPE=optional see README.md


================================================
FILE: .formatter.exs
================================================
# Used by "mix format"
[
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: mix
  directory: "/"
  schedule:
    interval: monthly
    time: "17:00"
    timezone: Europe/London


================================================
FILE: .github/workflows/ci.yml
================================================
name: Elixir CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    name: Build and test
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Elixir
      uses: erlef/setup-beam@v1
      with:
        elixir-version: '1.14.2' # Define the elixir version [required]
        otp-version: '25.1.2' # Define the OTP version [required]
    - name: Restore dependencies cache
      uses: actions/cache@v4
      with:
        path: deps
        key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
        restore-keys: ${{ runner.os }}-mix-
    - name: Install dependencies
      run: mix deps.get
    - name: Run Tests
      run: mix coveralls.json
      env:
        MIX_ENV: test
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v1



================================================
FILE: .gitignore
================================================
/_build
/cover
/deps
/doc
/.fetch
erl_crash.dump
*.ez
*.beam
/config/*.secret.exs
.elixir_ls
.env
elixir_auth_google.iml
.idea

================================================
FILE: .travis.yml
================================================
language: elixir
matrix:
  include:
    - elixir: 1.12.3
      otp_release: 24.0.2
env:
  MIX_ENV=test
script:
  - mix coveralls.json
after_success:
  - bash <(curl -s https://codecov.io/bash) # send coverage report
cache:
  directories:
    - _build
    - deps


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.


================================================
FILE: README.md
================================================
<div align="center">

# `elixir-auth-google`

The _easiest_ way to add Google OAuth authentication to your Elixir Apps.

![sign-in-with-google-buttons](https://user-images.githubusercontent.com/194400/69637172-07a67900-1050-11ea-9e25-2b9e84a49d91.png)

![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dwyl/elixir-auth-google/ci.yml?label=build&style=flat-square&branch=main)
[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/elixir-auth-google/master.svg?style=flat-square)](http://codecov.io/github/dwyl/elixir-auth-google?branch=main)
[![Hex.pm](https://img.shields.io/hexpm/v/elixir_auth_google?color=brightgreen&style=flat-square)](https://hex.pm/packages/elixir_auth_google)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/elixir-auth-google/issues)
[![HitCount](http://hits.dwyl.com/dwyl/elixir-auth-google.svg)](http://hits.dwyl.com/dwyl/elixir-auth-google)
<!-- [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-brightgreen.svg?style=flat-square)](https://GitHub.com/TutorialsAndroid/GButton) -->

</div>

# _Why_? 🤷

We needed a **_much_ simpler**
and **_extensively_ documented** way
to add "_**Sign-in** with **Google**_"
capability to our Elixir App(s). <br />

# _What_? 💭

An Elixir package that seamlessly handles
Google OAuth2 Authentication/Authorization
in as few steps as possible. <br />
Following best practices for security & privacy
and avoiding complexity
by having sensible defaults for all settings.


> We built a lightweight solution
that only does _one_ thing
and is easy for complete beginners to understand/use. <br />
There were already _several_ available options
for adding Google Auth to apps on
[hex.pm/packages?search=google](https://hex.pm/packages?search=google) <br />
that all added _far_ too many implementation steps (complexity)
and had incomplete docs (**`@doc false`**) and tests. <br />
e.g:
[github.com/googleapis/elixir-google-api](https://github.com/googleapis/elixir-google-api)
which is a
["_generated_"](https://github.com/googleapis/elixir-google-api/blob/master/scripts/generate_client.sh)
client and is considered "experimental". <br />
We have drawn inspiration from several sources
including code from other programming languages to build this package.
This result is _much_ simpler
than anything else
and has both step-by-step instructions
and a _complete working example_ App
including how to encrypt tokens for secure storage
to help you ship your app _fast_.


# _Who_? 👥

This module is for people building apps using Elixir/Phoenix
who want to ship the "Sign-in with Google" feature _faster_
and more maintainably.

It's targetted at _complete_ beginners
with no prior experience/knowledge
of auth "schemes" or "strategies". <br />
Just follow the detailed instructions
and you'll be up-and running in 5 minutes.


# _How_? ✅

You can add Google Authentication to your Elixir App
using **`elixir_auth_google`** <br />
in under **5 minutes**
by following these **5 _easy_ steps**:

## 1. Add the hex package to `deps` 📦

Open your project's **`mix.exs`** file
and locate the **`deps`** (dependencies) section. <br />
Add a line for **`:elixir_auth_google`** in the **`deps`** list:

```elixir
def deps do
  [
    {:elixir_auth_google, "~> 1.6.9"}
  ]
end
```

Once you have added the line to your **`mix.exs`**,
remember to run the **`mix deps.get`** command
in your terminal
to _download_ the dependencies.


## 2. Create Google APIs Application OAuth2 Credentials 🆕

Create a Google Application if you don't already have one,
generate the OAuth2 Credentials for the application
and save the credentials as environment variables
accessible by your app, or put them in your config file.

> **Note**: There are a few steps for creating a set of Google APIs credentials,
so if you don't already have a Google App,
we created the following step-by-step guide
to make it quick and _relatively_ painless:
[create-google-app-guide.md](https://github.com/dwyl/elixir-auth-google/blob/master/create-google-app-guide.md) <br />
Don't be intimidated by all the buzz-words;
it's quite straightforward.
And if you get stuck, ask for
[help!](https://github.com/dwyl/elixir-auth-google/issues)


## 3. Setup CLIENT_ID and CLIENT_SECRET in your project

You may either add those keys as environment variables or put them in the config:

```
export GOOGLE_CLIENT_ID=631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com
export GOOGLE_CLIENT_SECRET=MHxv6-RGF5nheXnxh1b0LNDq
```
Or add the following in the config file:

```elixir
config :elixir_auth_google,
  client_id: "631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com",
  client_secret: "MHxv6-RGF5nheXnxh1b0LNDq"

```
> ⚠️ Don't worry, these keys aren't valid.
They are just here for illustration purposes.


## 4. Create a `GoogleAuthController` in your Project 📝

Create a new file called
[`lib/app_web/controllers/google_auth_controller.ex`](https://github.com/dwyl/elixir-auth-google-demo/blob/master/lib/app_web/controllers/google_auth_controller.ex)
and add the following code:

```elixir
defmodule AppWeb.GoogleAuthController do
  use AppWeb, :controller

  @doc """
  `index/2` handles the callback from Google Auth API redirect.
  """
  def index(conn, %{"code" => code}) do
    {:ok, token} = ElixirAuthGoogle.get_token(code, MyAppWeb.Endpoint.url())
    {:ok, profile} = ElixirAuthGoogle.get_user_profile(token.access_token)
    conn
    |> put_view(AppWeb.PageView)
    |> render(:welcome, profile: profile)
  end
end
```
This code does 3 things:
+ Create a one-time auth `token` based on the response `code` sent by Google
after the person authenticates.
+ Request the person's profile data from Google based on the `access_token`
+ Render a `:welcome` view displaying some profile data
to confirm that login with Google was successful.


## 5. Create the `/auth/google/callback` Endpoint 📍

Open your **`router.ex`** file
and locate the section that looks like `scope "/", AppWeb do`

Add the following line:

```elixir
get "/auth/google/callback", GoogleAuthController, :index
```

Sample: [lib/app_web/router.ex#L20](https://github.com/dwyl/elixir-auth-google-demo/blob/4bb616dd134f498b84f079104c0f3345769517c4/lib/app_web/router.ex#L20)

### Different callback url?

You can specify the env var

```
export GOOGLE_CALLBACK_PATH=/myauth/google_callback
```

or add it in the configuration

Or add the following in the config file:

```elixir
config :elixir_auth_google,
  # ...
  callback_path: "/myauth/google_callback"
```

## 6. Add the "Login with Google" Button to your Template ✨

In order to display the "Sign-in with Google" button in the UI,
we need to _generate_ the URL for the button in the relevant controller,
and pass it to the template.

Open the `lib/app_web/controllers/page_controller.ex` file
and update the `index` function:

From:
```elixir
def index(conn, _params) do
  render(conn, "index.html")
end
```

To:
```elixir
def index(conn, _params) do
  oauth_google_url = ElixirAuthGoogle.generate_oauth_url(conn)
  render(conn, "index.html",[oauth_google_url: oauth_google_url])
end
```

You can add extra features the the generated url by passing the state as a string,
or any other query as a map of key/value pairs.

```elixir
oauth_google_url = ElixirAuthGoogle.generate_oauth_url(conn, state)
```

Will result in Google Sign In link with `&state=state` added.
And something like:

```elixir
oauth_google_url = ElixirAuthGoogle.generate_oauth_url(conn, %{lang: 'pt-BR'})
```

Will return a url with `lang=pt-BR` included in the sign in request.

#### _Alternatively_ pass the `url` of your `App` into `generate_oauth_url/1`

We have noticed that on `fly.io` 
where the `Phoenix` App is proxied,
passing the `conn` struct 
to `ElixirAuthGoogle.generate_oauth_url/2`
is not effective.
See [dwyl/elixir-auth-google/issues/94](https://github.com/dwyl/elixir-auth-google/issues/94)

So we added an alternative way 
of invoking `generate_oauth_url/2`
passing in the `url` of your `App`:

```elixir
def index(conn, _params) do
  base_url = MyAppWeb.Endpoint.url()
  oauth_google_url = ElixirAuthGoogle.generate_oauth_url(base_url)
  render(conn, "index.html",[oauth_google_url: oauth_google_url])
end
```

This uses 
[Phoenix.Endpoint.url/0](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#c:url/0) 
which is available in any `Phoenix` App.

Just remember to replace `MyAppWeb` with the name of your `App`. 😉

<br />

### 6.1 Update the `page/index.html.eex` Template

Open the `/lib/app_web/templates/page/index.html.eex` file
and type the following code:

```html
<section class="phx-hero">
  <h1>Welcome to Awesome App!</h1>
  <p>To get started, login to your Google Account: </p>
  <a href={@oauth_google_url}>
    <img src="https://i.imgur.com/Kagbzkq.png" alt="Sign in with Google" />
  </a>
</section>
```

# _Done_! 🚀

The home page of the app now has a big "Sign in with Google" button:

![sign-in-button](https://user-images.githubusercontent.com/194400/70202961-3c32c880-1713-11ea-9737-9121030ace06.png)

When the person clicks the button,
and authenticates with their Google Account,
they will be returned to your App
where you can display a "login success" message:

![welcome](https://user-images.githubusercontent.com/194400/70201692-494db880-170f-11ea-9776-0ffd1fdf5a72.png)


### _Optional_: Scopes

Most of the time you will only want/need
the person's email address and profile data
when authenticating with your App.
In the cases where you need more specific access
to a Google service, you will need to specify the exact scopes.
See:
https://developers.google.com/identity/protocols/oauth2/scopes

Once you know the scope(s) your App needs access to,
simply define them using an environment variable, e.g:

```
GOOGLE_SCOPE="email contacts photoslibrary"
```
Those double-quotes (`"`) encapsulating the environment variable `String` are important. 
Without them your system will only assign the first word to the `GOOGLE_SCOPE`.
e.g: `email` and ignore the remaining scopes you need.
***or*** you can set them as a config variable if you prefer:

```
config :elixir_auth_google,
  :google_scope: "email contacts photoslibrary"
```

With that configured, your App will gain access to the requested services
once the person authenticates/authorizes.

<br /> <br />


## _Optimised_ SVG+CSS Button

In **step 6.1** above, we suggest using an `<img>`
for the `Sign in with GitHub` button.

But even though this image appears small **`389 × 93 px`**
https://i.imgur.com/Kagbzkq.png it is "only" **`8kb`**:

![google-button-8kb](https://user-images.githubusercontent.com/194400/73607428-cd0c1000-45ad-11ea-8639-ffc3e9a0e0a2.png)

We could spend some time in a graphics editor optimising the image,
but we _know_ we can do better by using our `CSS` skills! 💡

> **Note**: This is the _official_ button provided by Google:
[developers.google.com/identity/images/signin-assets.zip](developers.google.com/identity/images/signin-assets.zip) <br />
So if there was any optimisation they could squeeze out of it,
they probably would have done it before publishing the zip!

The following code re-creates the `<img>`
using the GitHub logo **`SVG`**
and `CSS` for layout/style:

```html
<div style="display:flex; flex-direction:column; width:368px; margin-left:133px;">
  <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap">

  <a href="<%= @oauth_google_url %>"
    style="display:inline-flex; align-items:center; min-height:50px;
      background-color:#4285F4; font-family:'Roboto',sans-serif;
      font-size:28px; color:white; text-decoration:none;
      margin-top: 12px">
      <div style="background-color: white; margin:2px; padding-top:18px; padding-bottom:6px; min-height:59px; width:72px">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 533.5 544.3"
        width="52px" height="35" style="display:inline-flex; align-items:center;" >
        <path d="M533.5 278.4c0-18.5-1.5-37.1-4.7-55.3H272.1v104.8h147c-6.1 33.8-25.7 63.7-54.4 82.7v68h87.7c51.5-47.4 81.1-117.4 81.1-200.2z" fill="#4285f4"/>
        <path d="M272.1 544.3c73.4 0 135.3-24.1 180.4-65.7l-87.7-68c-24.4 16.6-55.9 26-92.6 26-71 0-131.2-47.9-152.8-112.3H28.9v70.1c46.2 91.9 140.3 149.9 243.2 149.9z" fill="#34a853"/>
        <path d="M119.3 324.3c-11.4-33.8-11.4-70.4 0-104.2V150H28.9c-38.6 76.9-38.6 167.5 0 244.4l90.4-70.1z" fill="#fbbc04"/>
        <path d="M272.1 107.7c38.8-.6 76.3 14 104.4 40.8l77.7-77.7C405 24.6 339.7-.8 272.1 0 169.2 0 75.1 58 28.9 150l90.4 70.1c21.5-64.5 81.8-112.4 152.8-112.4z" fill="#ea4335"/>
      </svg>
    </div>
    <div style="margin-left: 27px;">
      Sign in with Google
    </div>
  </a>
</div>
```

> We created this from scratch using the SVG of the Google logo
and some basic CSS. <br />
For the "making of" journey see:
https://github.com/dwyl/elixir-auth-google/issues/25

The result looks _better_ than the `<img>` button:

![img-vs-svg-8kb-1kb](https://user-images.githubusercontent.com/194400/73607841-54a84d80-45b3-11ea-9d0c-a81005a0bfde.png)

It can be scaled to any screen size so it will _always_ look great! <br />
Using http://bytesizematters.com we see that our SVG+CSS button is only **`1kb`**:
![bytesize-matters-google-button](https://user-images.githubusercontent.com/194400/73607378-4fe09b00-45ad-11ea-9ab1-3b383c1d4516.png)


That is an **87.5%** bandwidth saving
on the **`8kb`** of the
[**`.png`** button](https://github.com/dwyl/elixir-auth-google/issues/25).
And what's _more_ it reduces the number of HTTP requests
which means the page loads _even_ faster.

This is used in the Demo app:
[`lib/app_web/templates/page/index.html.eex`](https://github.com/dwyl/elixir-auth-google-demo/blob/4fdbeada2f13f4dd27d2372a916764ec7aad24e7/lib/app_web/templates/page/index.html.eex#L5-L26)


### `i18n`

The _biggest_ advantage of having an SVG+CSS button
is that you can _translate_ the button text! <br />
Since the text/copy of the button is now _just_ text in standard HTML,
the user's web browser can _automatically_ translate it! <br />
e.g: _French_ 🇬🇧 > 🇫🇷

![google-login-french-translation](https://user-images.githubusercontent.com/194400/73607961-c03eea80-45b4-11ea-9840-5d5f02ff8a13.png)

This is _much_ better UX for the **80%** of people in the world
who do _not_ speak English _fluently_.
The _single_ biggest engine for growth in startup companies
is [_translating_](https://youtu.be/T9ikpoF2GH0?t=463)
their user interface into more languages.
Obviously don't focus on translations
while you're building your MVP,
but if it's no extra _work_
to use this SVG+CSS button
and it means the person's web browser
can _automatically_ localise your App!

### _Accessibility_

The `SVG+CSS` button is more accessible than the image.
Even thought the `<img>` had an `alt` attribute
which is a lot better than nothing,
the `SVG+CSS` button can be re-interpreted
by a non-screen device and more easily transformed.


<br /> <br />

## _Even_ More Detail 💡

If you want to dive a bit deeper into _understanding_ how this package works,
You can read and grok the code in under 10 minutes:
[`/lib/elixir_auth_google.ex`](https://github.com/dwyl/elixir-auth-google/blob/master/lib/elixir_auth_google.ex)

We created a _basic_ demo Phoenix App,
to show you _exactly_ how you can implement
the **`elixir_auth_google`** package:
https://github.com/dwyl/elixir-auth-google-demo
It's deployed to Heroku: https://elixir-auth-google-demo.herokuapp.com <br />
(_no data is saved so you can play with it - and try to break it!_)

And if you want/need a more complete real-world example
including creating sessions and saving profile data to a database,
take a look at our MVP:
https://github.com/dwyl/app-mvp-phoenix


<br /><br />

## Notes 📝

+ Official Docs for Google Identity Platform:
https://developers.google.com/identity/choose-auth
  + Web specific sample code (JS):
  https://developers.google.com/identity/sign-in/web
+ Google Sign-In for server-side apps:
https://developers.google.com/identity/sign-in/web/server-side-flow
+ Using OAuth 2.0 for Web Server Applications:
https://developers.google.com/identity/protocols/OAuth2WebServer
+ Google Auth Branding Guidelines:
https://developers.google.com/identity/branding-guidelines <br />
Only two colors are permitted for the button:
**white** `#FFFFFF` and **blue** `#4285F4`

![two-colors-of-google-auth-button](https://user-images.githubusercontent.com/194400/69634312-d9be3600-1049-11ea-9354-cdaa53f5c42b.png)


### Fun Facts 📈📊

Unlike other "social media" companies,
Google/Alphabet does not report it's
_Monthly_ Active Users (MAUs)
or _Daily_ Active Users (DAUs)
however they do release stats in drips
in their Google IO or YouTube events.
The following is a quick list of facts
that make adding Google Auth to your App
a compelling business case:

+ As of May 2019, there are over
[2.5 Billion](https://www.theverge.com/2019/5/7/18528297/google-io-2019-android-devices-play-store-total-number-statistic-keynote)
_active_ Android devices;
[87%](https://www.idc.com/promo/smartphone-market-share/os) global market share.
All these people have Google Accounts in order to use Google services.
+ YouTube has
[2 billion](https://www.businessofapps.com/data/youtube-statistics/)
monthly active YouTube users (_signed in with a Google Account_).
+ Gmail has
[1.5 Billion](https://www.thenewsminute.com/article/googles-gmail-turns-15-now-has-over-15-billion-monthly-active-users-99275)
monthly active users a
[27% share](https://seotribunal.com/blog/google-stats-and-facts)
 of the global email client market.
+ [65%](https://techjury.net/stats-about/gmail-statistics)
of Small and Medium sized businesses use Google Apps for business.
+ [90%+](https://techjury.net/stats-about/gmail-statistics)
of startups use Gmail. This is a good _proxy_ for "early adopters".
+ [68%](https://eu.azcentral.com/story/opinion/op-ed/joannaallhands/2017/10/09/google-classroom-changing-teachers-students-education/708246001/)
of schools in the US use Google Classroom and related G-suite products. <br />
So the _next_ generation of internet/app users have Google accounts.
+ Google has
[90.46%](https://seotribunal.com/blog/google-stats-and-facts/)
of the search engine market share worldwide. 95.4% on Mobile.

Of the 4.5 billion internet users (58% of the world population),
around 3.2 billion (72%) have a Google account.
90%+ of tech "early adopters" use Google Apps
which means that adding Google OAuth Sign-in
is the _logical_ choice for _most_ Apps.

### Privacy Concerns? 🔐

A _common misconception_ is that adding Google Auth Sign-in
sends a user's application data to Google.
This is **`false`** and App developers have 100% control
over what data is sent to (stored by) Google.
An App can use Google Auth to _authenticate_ a person
(_identify them and get read-only access
  to their personal details like **first name** and **email address**_)
without sending any data to Google.
Yes, it will mean that Google "knows" that the person is _using_ your App,
but it will not give Google any insight into _how_ they are using it
or what types of data they are storing in the App. Privacy is maintained.
So if you use the @dwyl app to plan your wedding or next holiday,
Google will not have _any_ of that data
and will not serve any annoying ads based on your project/plans.


================================================
FILE: config/config.exs
================================================
import Config

if Mix.env() == :test do
  import_config "test.exs"
end


================================================
FILE: config/test.exs
================================================
import Config

config :elixir_auth_google,
  client_id: "631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com",
  client_secret: "MHxv6-RGF5nheXnxh1b0LNDq",
  httpoison_mock: true

System.put_env(
  "GOOGLE_CLIENT_ID",
  "631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com"
)


================================================
FILE: create-google-app-guide.md
================================================
# Creating a Google Application for OAuth2 Authentication

This is a step-by-step guide
for creating a Google App from scratch
so that you can obtain the API keys
to add Google OAuth2 Authentication
to your Elixir App
and save the credentials to environment variables.<br />
Our guide follows the _official_ docs:
https://developers.google.com/identity/sign-in/web/server-side-flow <br />
We've added detail and screenshots to the steps
because some people have found the official Google API docs confusing. <br />
_This_ guide is checked periodically by the @dwyl team/community,
but Google are known to occasionally change their UI/Workflow,
so if anything has changed, or there are extra/fewer steps,
[please let us know!](https://github.com/dwyl/elixir-auth-google/issues)

## 1. Create a New Project

In your preferred web browser,
visit:
https://console.developers.google.com
and ensure you are authenticated with your Google Account
so you can see your "API & Services Dashboard":

<img width="972" alt="elixir-auth-google-create-new-app" src="https://user-images.githubusercontent.com/194400/69722359-c7f29680-110e-11ea-90ae-da11ba42cc4a.png">

If you don't already have a Google APIs project for your Elixir App,
click the **CREATE** button on the dashboard.


## 2. Define the Details for your New Project (App)

Enter the details for your App's **Project name**
and where appropriate input any additional/relevant info:

<img width="972" alt="elixir-auth-google-app-details" src="https://user-images.githubusercontent.com/194400/69722801-ead17a80-110f-11ea-9bc9-d145b29baac6.png">

Click the **CREATE** button to create your project.


## 3. OAuth Consent Screen

After creating the New Project,
the UI will return to the APIs dashboard
and the name of your app will appear in the top menu.

Click the **OAuth Consent Screen** from the left side menu:

<img width="959" alt="elixir-auth-google-consent-screen" src="https://user-images.githubusercontent.com/194400/69727668-e78fbc00-111a-11ea-8b39-124a4b045b02.png">

Make the Application **`Public`** (_the default option_) and
input the same name as you used for your application in step 1.
Upload an image if you have one (_e.g: the icon/logo for your app_):

<img width="1226" alt="OAuth-consent-screen-1of2" src="https://user-images.githubusercontent.com/194400/69727443-76500900-111a-11ea-9425-bff972afe565.png">

Leave the "**Scopes for Google APIs**" set to the default
**email**, **profile** and **openid**.

No other data is required at this point, so skip the rest.

Scroll down to the bottom and click "**Save**":
<img width="1277" alt="OAuth-consent-screen-2of2" src="https://user-images.githubusercontent.com/194400/69727445-76e89f80-111a-11ea-860d-fd41939dd10f.png">

This will take you to the API Credentials page.

## 4. Create Credentials

Click the **Create Credentials** button:
<img width="960" alt="Screenshot 2019-11-26 at 23 24 28" src="https://user-images.githubusercontent.com/194400/69725288-b2cd3600-1115-11ea-9601-04054cac6f5b.png">
That will popup a menu from which you will select **OAuth Client ID**.

You will see a form that allows you to specify
the details of your App for the credentials.

<img width="971" alt="Screenshot 2019-11-27 at 02 13 55" src="https://user-images.githubusercontent.com/194400/69728313-296d3200-111c-11ea-8da0-85c1ade89d8a.png">

+ Application Type: Web application
+ Name: Elixir Auth Server
+ Authorized JavaScript origins:
http://localhost:4000
(_the default for a Phoenix app on your local dev machine.
  you can add your "production" URL later._)
+ Authorized redirect URIs:  
http://localhost:4000/auth/google/callback
(_the endpoint to redirect to once authentication is successful.
  again, add your production URL once you have auth working on `localhost`_)

> Ensure you hit the enter key after pasting/typing
the URIs to ensure they are saved.
A common stumbling block is that URIs aren't saved. See:
https://stackoverflow.com/questions/24363041/redirect-uri-in-google-cloud-console-doesnt-save

Once you have input the relevant data click the **Create** button.

> This form/step can be confusing at first,
but essentially you can have multiple credentials
for the same project,
e.g: if you had a Native Android App
you would create a new set of credentials
to ensure a separation of concerns between
server and client implementations.
For now just create the server (Elixir) credentials.


## 5. Download the OAuth Client Credentials

After you click the **Create** button
in the **Create OAuth client ID** form (_step 4 above_),
you will be shown your OAuth client Credentials:

<img width="970" alt="elixir-auth-google-oauth-client-credentials" src="https://user-images.githubusercontent.com/194400/69730648-5cb1c000-1120-11ea-8ad4-0ef62cfb6a0a.png">

Download the credentials, e.g:

+ Client ID: 631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com
+ Client Secret: MHxv6-RGF5nheXnxh1b0LNDq

> ⚠️ Don't worry, these keys aren't valid.
We deleted them immediately after capturing the screenshot
to avoid any security issues.
Obviously treat your credentials
like you would the username+password for your bank account;
never share a **real** Client ID or secret on GitHub
or any other public/insecure forum!

You can also download the OAuth credentials as a json file:

<img width="821" alt="elixir-auth-google-json" src="https://user-images.githubusercontent.com/194400/69736916-57a63e00-112b-11ea-8b28-6f137f00106b.png">

Example:
```json
{
  "web": {
    "client_id": "631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleusercontent.com",
    "project_id": "elixir-auth-demo",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_secret": "MHxv6-RGF5nheXnxh1b0LNDq",
    "redirect_uris": [
      "http://localhost:4000/auth/google/callback"
    ],
    "javascript_origins": [
      "http://localhost:4000"
    ]
  }
}
```

> Again, for security reasons,
these credentials were
invalidated _immediately_ after downloading. <br />
But this is what the file looks like.


Return to step 3 of the
[README.md](https://github.com/dwyl/elixir-auth-google/blob/master/README.md)


<br />

# Note

When you ship your app to your Production environment,
you will need to re-visit steps 3 & 4
to update your app settings URL & callback
to reflect the URl where you are deploying your app e.g:

![add-heroku-app](https://user-images.githubusercontent.com/194400/70204921-32f92a00-171a-11ea-83b2-34e5eeea777b.png)

In our case
https://elixir-auth-google-demo.herokuapp.com
and
https://elixir-auth-google-demo.herokuapp.com/auth/google/callback


================================================
FILE: lib/elixir_auth_google.ex
================================================
defmodule ElixirAuthGoogle do
  @moduledoc """
  Minimalist Google OAuth Authentication for Elixir Apps.
  Extensively tested, documented, maintained and in active use in production.
  """
  @google_auth_url "https://accounts.google.com/o/oauth2/v2/auth?response_type=code"
  @google_token_url "https://oauth2.googleapis.com/token"
  @google_user_profile "https://www.googleapis.com/oauth2/v3/userinfo"
  @default_scope "profile email"
  @default_callback_path "/auth/google/callback"

  @httpoison (Application.compile_env(:elixir_auth_google, :httpoison_mock) &&
                ElixirAuthGoogle.HTTPoisonMock) || HTTPoison

  @type conn :: map
  @type url :: String.t()

  @doc """
  `inject_poison/0` injects a TestDouble of HTTPoison in Test
  so that we don't have duplicate mock in consuming apps.
  see: github.com/dwyl/elixir-auth-google/issues/35
  """
  def inject_poison, do: @httpoison

  @doc """
  `get_baseurl_from_conn/1` derives the base URL from the conn struct
  """
  @spec get_baseurl_from_conn(conn) :: String.t()
  def get_baseurl_from_conn(%{host: h, port: p, scheme: s}) when p != 80 do
    "#{Atom.to_string(s)}://#{h}:#{p}"
  end

  def get_baseurl_from_conn(%{host: h, scheme: s}) do
    "#{Atom.to_string(s)}://#{h}"
  end

  def get_baseurl_from_conn(%{host: h} = conn) do
    scheme =
      case h do
        "localhost" -> :http
        _ -> :https
      end

    get_baseurl_from_conn(Map.put(conn, :scheme, scheme))
  end

  @doc """
  `generate_redirect_uri/1` generates the Google redirect uri based on `conn`
  or the `url`. If the `App.Endpoint.url()`
  e.g: auth.dwyl.com or https://gcal.fly.dev
  is passed into `generate_redirect_uri/1`,
  return that `url` with the callback appended to it.
  See: github.com/dwyl/elixir-auth-google/issues/94
  """
  @spec generate_redirect_uri(url) :: String.t()
  def generate_redirect_uri(url) when is_binary(url) do
    scheme =
      cond do
        # url already contains scheme return empty
        String.contains?(url, "https") -> ""
        # url contains ":" is localhost:4000 no need for scheme
        String.contains?(url, ":") -> ""
        # Default to https if scheme not set e.g: app.fly.dev -> https://app.fly.fev
        true -> "https://"
      end

    "#{scheme}#{url}" <> get_app_callback_url()
  end

  @spec generate_redirect_uri(conn) :: String.t()
  def generate_redirect_uri(conn) do
    get_baseurl_from_conn(conn) <> get_app_callback_url()
  end

  @doc """
  `generate_oauth_url/1` creates the Google OAuth2 URL with client_id, scope and
  redirect_uri which is the URL Google will redirect to when auth is successful.
  This is the URL you need to use for your "Login with Google" button.
  See step 5 of the instructions.
  """
  @spec generate_oauth_url(String.t()) :: String.t()
  def generate_oauth_url(url) when is_binary(url) do
    query = [
      client_id: google_client_id(),
      redirect_uri: generate_redirect_uri(url),
      scope: google_scope()
    ]

    params = URI.encode_query(query, :rfc3986)

    "#{@google_auth_url}&#{params}"
  end

  @spec generate_oauth_url(conn) :: String.t()
  def generate_oauth_url(conn) when is_map(conn) do
    query = [
      client_id: google_client_id(),
      redirect_uri: generate_redirect_uri(conn),
      scope: google_scope()
    ]

    params = URI.encode_query(query, :rfc3986)

    "#{@google_auth_url}&#{params}"
  end

  @doc """
  Same as `generate_oauth_url/1` with `state` query parameter,
  or a `map` of key/pair values to be included in the urls query string.
  """
  @spec generate_oauth_url(conn, String.t() | map) :: String.t()
  def generate_oauth_url(conn, state) when is_binary(state) do
    params = URI.encode_query(%{state: state}, :rfc3986)
    generate_oauth_url(conn) <> "&#{params}"
  end

  def generate_oauth_url(conn, query) when is_map(query) do
    query = URI.encode_query(query, :rfc3986)
    generate_oauth_url(conn) <> "&#{query}"
  end

  @doc """
  `get_token/2` encodes the secret keys and authorization code returned by Google
  and issues an HTTP request to get a person's profile data.

  **TODO**: we still need to handle the various failure conditions >> issues/16
  """
  @spec get_token(String.t(), conn) :: {:ok, map} | {:error, any}
  def get_token(code, conn) when is_map(conn) do
    redirect_uri = generate_redirect_uri(conn)

    inject_poison().post(@google_token_url, req_body(code, redirect_uri))
    |> parse_body_response()
  end

  @spec get_token(String.t(), url) :: {:ok, map} | {:error, any}
  def get_token(code, url) when is_binary(url) do
    redirect_uri = generate_redirect_uri(url)

    inject_poison().post(@google_token_url, req_body(code, redirect_uri))
    |> parse_body_response()
  end

  defp req_body(code, redirect_uri) do
    Jason.encode!(%{
      client_id: google_client_id(),
      client_secret: google_client_secret(),
      redirect_uri: redirect_uri,
      grant_type: "authorization_code",
      code: code
    })
  end

  @doc """
  `get_user_profile/1` requests the Google User's userinfo profile data
  providing the access_token received in the `get_token/1` above.
  invokes `parse_body_response/1` to decode the JSON data.

  **TODO**: we still need to handle the various failure conditions >> issues/16
  At this point the types of errors we expect are HTTP 40x/50x responses.
  """
  @spec get_user_profile(String.t()) :: {:ok, map} | {:error, any}
  def get_user_profile(token) do
    params = URI.encode_query(%{access_token: token}, :rfc3986)

    "#{@google_user_profile}?#{params}"
    |> inject_poison().get()
    |> parse_body_response()
  end

  @doc """
  `parse_body_response/1` parses the response returned by Google
  so your app can use the resulting JSON.
  """
  @spec parse_body_response({atom, String.t()} | {:error, any}) :: {:ok, map} | {:error, any}
  def parse_body_response({:error, err}), do: {:error, err}

  def parse_body_response({:ok, response}) do
    body = Map.get(response, :body)
    # make keys of map atoms for easier access in templates
    if body == nil do
      {:error, :no_body}
    else
      {:ok, str_key_map} = Jason.decode(body)
      atom_key_map = for {key, val} <- str_key_map, into: %{}, do: {String.to_atom(key), val}
      {:ok, atom_key_map}
    end

    # https://stackoverflow.com/questions/31990134
  end

  def google_client_id do
    System.get_env("GOOGLE_CLIENT_ID") || Application.get_env(:elixir_auth_google, :client_id)
  end

  defp google_client_secret do
    System.get_env("GOOGLE_CLIENT_SECRET") ||
      Application.get_env(:elixir_auth_google, :client_secret)
  end

  defp google_scope do
    System.get_env("GOOGLE_SCOPE") || Application.get_env(:elixir_auth_google, :google_scope) ||
      @default_scope
  end

  defp get_app_callback_url do
    System.get_env("GOOGLE_CALLBACK_PATH") ||
      Application.get_env(:elixir_auth_google, :callback_path) || @default_callback_path
  end
end


================================================
FILE: lib/httpoison_mock.ex
================================================
defmodule ElixirAuthGoogle.HTTPoisonMock do
  @moduledoc """
  This is a TestDouble for HTTPoison which returns a predictable response.
  Please see: https://github.com/dwyl/elixir-auth-google/issues/35
  """

  @doc """
  get/1 passing in the wrong_token is used to test failure in the auth process.
  Obviously, don't invoke it from your App unless you want people to see fails.
  """
  def get("https://www.googleapis.com/oauth2/v3/userinfo?access_token=wrong_token") do
    {:error, :bad_request}
  end

  # get/1 using a dummy _url to test body decoding.
  def get(_url) do
    {:ok,
     %{
       body:
         Jason.encode!(%{
           email: "nelson@gmail.com",
           email_verified: true,
           family_name: "Correia",
           given_name: "Nelson",
           locale: "en",
           name: "Nelson Correia",
           picture: "https://lh3.googleusercontent.com/a-/AAuE7mApnYb260YC1JY7a",
           sub: "940732358705212133793"
         })
     }}
  end

  @doc """
  post/2 passing in dummy _url & _body to test return of access_token.
  """
  def post(_url, _body) do
    {:ok, %{body: Jason.encode!(%{access_token: "token1"})}}
  end
end


================================================
FILE: mix.exs
================================================
defmodule ElixirAuthGoogle.MixProject do
  use Mix.Project

  @description "Minimalist Google OAuth Authentication for Elixir Apps"
  @version "1.6.11"

  def project do
    [
      app: :elixir_auth_google,
      version: @version,
      elixir: ">= 1.11.0",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      description: @description,
      package: package(),
      aliases: aliases(),
      # coverage
      test_coverage: [tool: ExCoveralls]
    ]
  end

  def cli do
    [
      preferred_envs: [
        c: :test,
        coveralls: :test,
        "coveralls.json": :test,
        "coveralls.html": :test,
        t: :test
      ]
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:credo, "~> 1.5", only: [:dev, :test], runtime: false},
      {:httpoison, "~> 2.3"},
      {:jason, "~> 1.2"},

      # Track test coverage: github.com/parroty/excoveralls
      {:excoveralls, "~> 0.18.0", only: [:test, :dev]},

      # Mock stuffs in test: github.com/jjh42/mock
      {:mock, "~> 0.3.0", only: :test},

      # documentation
      {:ex_doc, "~> 0.40.0", only: :dev}
    ]
  end

  defp package do
    [
      maintainers: ["dwyl"],
      licenses: ["GPL-2.0-or-later"],
      links: %{github: "https://github.com/dwyl/elixir-auth-google"},
      files: ~w(lib LICENSE mix.exs README.md .formatter.exs)
    ]
  end

  defp aliases do
    [
      t: ["test"],
      c: ["coveralls.html"]
    ]
  end
end


================================================
FILE: test/elixir_auth_google_test.exs
================================================
defmodule ElixirAuthGoogleTest do
  use ExUnit.Case, async: true
  doctest ElixirAuthGoogle

  import Mock

  test "get_baseurl_from_conn(conn) detects the URL based on conn.host HTTP" do
    conn = %{
      host: "localhost",
      port: 4000,
      scheme: :http
    }

    assert ElixirAuthGoogle.get_baseurl_from_conn(conn) == "http://localhost:4000"
  end

  test "get_baseurl_from_conn(conn) detects the URL based on conn.host HTTPS" do
    conn = %{
      host: "localhost",
      port: 4000,
      scheme: :https
    }

    assert ElixirAuthGoogle.get_baseurl_from_conn(conn) == "https://localhost:4000"
  end

  test "get_baseurl_from_conn(conn) detects the URL for production HTTPS" do
    conn = %{
      host: "dwyl.com",
      port: 80,
      scheme: :https
    }

    assert ElixirAuthGoogle.get_baseurl_from_conn(conn) == "https://dwyl.com"
  end

  test "get_baseurl_from_conn(conn) detects the URL for production HTTP" do
    conn = %{
      host: "dwyl.com",
      port: 80,
      scheme: :http
    }

    assert ElixirAuthGoogle.get_baseurl_from_conn(conn) == "http://dwyl.com"
  end

  test "get_baseurl_from_conn(conn) detects the URL for production HTTPS, non-standard port" do
    conn = %{
      host: "dwyl.com",
      port: 8080,
      scheme: :https
    }

    assert ElixirAuthGoogle.get_baseurl_from_conn(conn) == "https://dwyl.com:8080"
  end

  test "get Google login url" do
    conn = %{
      host: "localhost",
      port: 4000
    }

    assert ElixirAuthGoogle.generate_oauth_url(conn) =~
             "https://accounts.google.com/o/oauth2/v2/auth?response_type=code"
  end

  test "get Google login url (config redirect uri)" do
    conn = %{
      host: "localhost",
      port: 4000
    }

    url = ElixirAuthGoogle.generate_oauth_url(conn)
    assert url =~ "https://accounts.google.com/o/oauth2/v2/auth?response_type=code"
    assert url =~ "http%3A%2F%2Flocalhost%3A4000"
  end

  test "get Google login url with state" do
    conn = %{
      host: "localhost",
      port: 4000
    }

    url = ElixirAuthGoogle.generate_oauth_url(conn, "state1")
    id = System.get_env("GOOGLE_CLIENT_ID")
    id_from_config = Application.get_env(:elixir_auth_google, :client_id)

    assert id == id_from_config

    expected =
      "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=" <>
        id <>
        "&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&state=state1"

    assert url == expected
  end

  test "get Google login with language parameters" do
    conn = %{
      host: "localhost",
      port: 4000
    }

    url = ElixirAuthGoogle.generate_oauth_url(conn, %{hl: "es-MX"})
    id = System.get_env("GOOGLE_CLIENT_ID")
    id_from_config = Application.get_env(:elixir_auth_google, :client_id)

    assert id == id_from_config

    expected =
      "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=" <>
        id <>
        "&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&hl=es-MX"

    assert url == expected
  end

  test "get Google token" do
    conn = %{
      host: "localhost",
      port: 4000
    }

    {:ok, res} = ElixirAuthGoogle.get_token("ok_code", conn)
    assert res == %{access_token: "token1"}
  end

  test "get Google token with url as second param #94" do
    {:ok, res} = ElixirAuthGoogle.get_token("ok_code", "gcal.fly.dev")
    assert res == %{access_token: "token1"}
  end

  test "get Google token (config redirect uri)" do
    conn = %{
      host: "localhost",
      port: 4000
    }

    {:ok, res} = ElixirAuthGoogle.get_token("ok_code", conn)
    assert res == %{access_token: "token1"}
  end

  test "get_user_profile/1" do
    res = %{
      email: "nelson@gmail.com",
      email_verified: true,
      family_name: "Correia",
      given_name: "Nelson",
      locale: "en",
      name: "Nelson Correia",
      picture: "https://lh3.googleusercontent.com/a-/AAuE7mApnYb260YC1JY7a",
      sub: "940732358705212133793"
    }

    assert ElixirAuthGoogle.get_user_profile("123") == {:ok, res}
  end

  test "return error with incorrect token" do
    assert ElixirAuthGoogle.get_user_profile("wrong_token") == {:error, :bad_request}
  end

  test "generate_redirect_uri(conn) generate correct callback url" do
    conn = %{
      host: "foobar.com",
      port: 80
    }

    assert ElixirAuthGoogle.generate_redirect_uri(conn) ==
             "https://foobar.com/auth/google/callback"
  end

  test "generate_oauth_url(url) passing in App.Endpoint.url() #94" do
    url = "gcal.fly.dev"
    client_id = ElixirAuthGoogle.google_client_id()
    https = "https%3A%2F%2F#{url}"

    auth_url =
      "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=#{client_id}&redirect_uri=#{https}%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email"

    assert ElixirAuthGoogle.generate_oauth_url(url) =~ auth_url
  end

  test "generate_oauth_url(url) with scheme e.g. https://gcal.fly.dev #94" do
    no_scheme = "gcal.fly.dev"
    url = "https://#{no_scheme}"
    client_id = ElixirAuthGoogle.google_client_id()
    https = "https%3A%2F%2F#{no_scheme}"

    auth_url =
      "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=#{client_id}&redirect_uri=#{https}%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email"

    assert ElixirAuthGoogle.generate_oauth_url(url) =~ auth_url
  end

  test "generate_redirect_uri(conn) generate correct callback url with custom url path from application environment variable" do
    conn = %{
      host: "foobar.com",
      port: 80
    }

    mock_get_env = fn :elixir_auth_google, :callback_path -> "/special/callback" end

    with_mock Application, get_env: mock_get_env do
      assert ElixirAuthGoogle.generate_redirect_uri(conn) ==
               "https://foobar.com/special/callback"
    end
  end

  test "generate_redirect_uri(conn) generate correct callback url with custom url path from system environment variable" do
    conn = %{
      host: "foobar.com",
      port: 80
    }

    mock_get_env = fn "GOOGLE_CALLBACK_PATH" -> "/very/special/callback" end

    with_mock System, get_env: mock_get_env do
      assert ElixirAuthGoogle.generate_redirect_uri(conn) ==
               "https://foobar.com/very/special/callback"
    end
  end
end


================================================
FILE: test/test_helper.exs
================================================
ExUnit.start()
Download .txt
gitextract_jwgh7fao/

├── .env_sample
├── .formatter.exs
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── config/
│   ├── config.exs
│   └── test.exs
├── create-google-app-guide.md
├── lib/
│   ├── elixir_auth_google.ex
│   └── httpoison_mock.ex
├── mix.exs
└── test/
    ├── elixir_auth_google_test.exs
    └── test_helper.exs
Download .txt
SYMBOL INDEX (25 symbols across 4 files)

FILE: lib/elixir_auth_google.ex
  class ElixirAuthGoogle (line 1) | defmodule ElixirAuthGoogle
    method inject_poison (line 23) | def inject_poison, do: @httpoison
    method get_baseurl_from_conn (line 33) | def get_baseurl_from_conn(%{host: h, scheme: s}) do
    method get_baseurl_from_conn (line 37) | def get_baseurl_from_conn(%{host: h} = conn) do
    method generate_redirect_uri (line 71) | def generate_redirect_uri(conn) do
    method req_body (line 144) | defp req_body(code, redirect_uri) do
    method get_user_profile (line 163) | def get_user_profile(token) do
    method parse_body_response (line 176) | def parse_body_response({:error, err}), do: {:error, err}
    method parse_body_response (line 178) | def parse_body_response({:ok, response}) do
    method google_client_id (line 192) | def google_client_id do
    method google_client_secret (line 196) | defp google_client_secret do
    method google_scope (line 201) | defp google_scope do
    method get_app_callback_url (line 206) | defp get_app_callback_url do

FILE: lib/httpoison_mock.ex
  class ElixirAuthGoogle.HTTPoisonMock (line 1) | defmodule ElixirAuthGoogle.HTTPoisonMock
    method get (line 11) | def get("https://www.googleapis.com/oauth2/v3/userinfo?access_token=wr...
    method get (line 16) | def get(_url) do
    method post (line 36) | def post(_url, _body) do

FILE: mix.exs
  class ElixirAuthGoogle.MixProject (line 1) | defmodule ElixirAuthGoogle.MixProject
    method project (line 7) | def project do
    method cli (line 22) | def cli do
    method application (line 35) | def application do
    method deps (line 42) | defp deps do
    method package (line 59) | defp package do
    method aliases (line 68) | defp aliases do

FILE: test/elixir_auth_google_test.exs
  class ElixirAuthGoogleTest (line 1) | defmodule ElixirAuthGoogleTest
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (65K chars).
[
  {
    "path": ".env_sample",
    "chars": 153,
    "preview": "export GOOGLE_CLIENT_ID=YourAppsClientId.apps.googleusercontent.com\nexport GOOGLE_CLIENT_SECRET=SuperSecret\n# export GOO"
  },
  {
    "path": ".formatter.exs",
    "chars": 97,
    "preview": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 142,
    "preview": "version: 2\nupdates:\n- package-ecosystem: mix\n  directory: \"/\"\n  schedule:\n    interval: monthly\n    time: \"17:00\"\n    ti"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 839,
    "preview": "name: Elixir CI\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  build:\n    name: Bui"
  },
  {
    "path": ".gitignore",
    "chars": 126,
    "preview": "/_build\n/cover\n/deps\n/doc\n/.fetch\nerl_crash.dump\n*.ez\n*.beam\n/config/*.secret.exs\n.elixir_ls\n.env\nelixir_auth_google.iml"
  },
  {
    "path": ".travis.yml",
    "chars": 262,
    "preview": "language: elixir\nmatrix:\n  include:\n    - elixir: 1.12.3\n      otp_release: 24.0.2\nenv:\n  MIX_ENV=test\nscript:\n  - mix c"
  },
  {
    "path": "LICENSE",
    "chars": 18092,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "README.md",
    "chars": 19412,
    "preview": "<div align=\"center\">\n\n# `elixir-auth-google`\n\nThe _easiest_ way to add Google OAuth authentication to your Elixir Apps.\n"
  },
  {
    "path": "config/config.exs",
    "chars": 71,
    "preview": "import Config\n\nif Mix.env() == :test do\n  import_config \"test.exs\"\nend\n"
  },
  {
    "path": "config/test.exs",
    "chars": 318,
    "preview": "import Config\n\nconfig :elixir_auth_google,\n  client_id: \"631770888008-6n0oruvsm16kbkqg6u76p5cv5kfkcekt.apps.googleuserco"
  },
  {
    "path": "create-google-app-guide.md",
    "chars": 6751,
    "preview": "# Creating a Google Application for OAuth2 Authentication\n\nThis is a step-by-step guide\nfor creating a Google App from s"
  },
  {
    "path": "lib/elixir_auth_google.ex",
    "chars": 6923,
    "preview": "defmodule ElixirAuthGoogle do\n  @moduledoc \"\"\"\n  Minimalist Google OAuth Authentication for Elixir Apps.\n  Extensively t"
  },
  {
    "path": "lib/httpoison_mock.ex",
    "chars": 1170,
    "preview": "defmodule ElixirAuthGoogle.HTTPoisonMock do\n  @moduledoc \"\"\"\n  This is a TestDouble for HTTPoison which returns a predic"
  },
  {
    "path": "mix.exs",
    "chars": 1608,
    "preview": "defmodule ElixirAuthGoogle.MixProject do\n  use Mix.Project\n\n  @description \"Minimalist Google OAuth Authentication for E"
  },
  {
    "path": "test/elixir_auth_google_test.exs",
    "chars": 6347,
    "preview": "defmodule ElixirAuthGoogleTest do\n  use ExUnit.Case, async: true\n  doctest ElixirAuthGoogle\n\n  import Mock\n\n  test \"get_"
  },
  {
    "path": "test/test_helper.exs",
    "chars": 15,
    "preview": "ExUnit.start()\n"
  }
]

About this extraction

This page contains the full source code of the dwyl/elixir-auth-google GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (60.9 KB), approximately 16.7k tokens, and a symbol index with 25 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!