Sign in with Apple and Google in Flutter App + Golang backend

Erik
Dreamwod tech
Published in
4 min readOct 24, 2021

This article describes the signup and sign-in flow for users in the Dreamwod Flutter app using AppleId and Google with a backend in Golang.

Sign up with Apple and Google

The Dreamwod app supports three ways of signing up, Email&Password, with Apple Id, or through Google. This article will describe the setup for AppleId and Google both in the flutter app as well in the golang backend.

Our requirements

  • Apple Id and Google should work on both iOS and Android (since users can have with multiple devices)
  • Use available flutter packages if they are available
  • No hard dependency on Firebase (i.e. we don’t store users in their database)
  • Backend in Golang

Sign in with Apple Id

Outline of the flow (on iOS):

  1. The user clicks SignIn with Apple.
  2. User confirms and an authentication token is received from Apple.
  3. The token is sent from the app to the backend.
  4. The backend validates the token to make sure it’s valid.
  5. The user is created or logged in.
  6. JWT access and refresh tokens are returned to the user.

The package sign_in_with_apple is the package that we are using for authentication in the app.

Sign in with Apple Button

The webAuthenticationOptions are needed if the user is signing in from an Android device, more about that later.

The sign-in request that is sent from the app to the backend looks like below. First and last name needs to be included since it’s not part of the authorization code. A flag if the request is from Android or not is also included since those need to be validated by a different ClientId in the backend.

final request = AppleSignInRequest(
firstName: credentials.givenName,
lastName: credentials.familyName,
authorizationCode: credentials.authorizationCode,
isAndroid: Platform.isAndroid,
);

The backend needs to validate the authorization code to make sure that it is valid. The go package go-signin-with-apple is used for validating and tokens. The information that is needed for validation is teamId, clientId, keyId, and the private key. We also use a different clientId for android, see below.

appleSignIn:
teamId: <...>
clientId: com.dreamwod.app
androidClientId: com.dreamwod.app.login
keyId: <...>
privateKey: |
-----BEGIN PRIVATE KEY-----
....
-----END PRIVATE KEY-----

The authenticator that is validating the token is shown below, the validation returns the unique id and the email of the user.

Validation of Apple authorization codes

The flow is pretty straightforward on iOS. It's a bit more complicated on Android devices since those are using a web-based flow.

Outline of the flow (on Android):

  1. The user clicks SignIn with Apple.
  2. A web view is opened and the user logs in with Apple credentials
  3. The user is redirected back to the redirectURI in the webAuthenticationOptions.
  4. Our server at redirectURI redirects the user back to the app with the payload from Apple
  5. Same steps as in the iOS flow…

Redirect URI

In our case, we have the server running and endpoint at auth/callbacks/apple-sign-in and when we receive a POST request the below code is executed and there is a temporary redirect back to the app. The AndroidManifest.xml needs to be updated with the instructions at sign_in_with_apple for it to work.

data, err := ioutil.ReadAll(c.Request.Body)

if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}

c.Redirect(http.StatusTemporaryRedirect, "intent://callback?"+string(data)+"#Intent;package=com.dreamwod.app;scheme=signinwithapple;end")

Sign in with Google

Sign in with Google

Outline of the flow (both iOS and Android):

  1. The user clicks SignIn with Google.
  2. User confirms and an id token is received.
  3. The token is sent from the app to the backend.
  4. The backend validates the token to make sure it’s valid.
  5. The user is created or logged in.
  6. JWT access and refresh tokens are returned to the user.

The package google_sign_in is the package that we are using for the sign-in.

Sign in with Google Button

The id token is returned from the sign-in operation and sent to the backend. We don’t get the first and the last name of the user in the same way as through Apple.

final request = GoogleSignInRequest(
idToken: idToken
);

The backend validates and the id token and retrieves email, first and last name. More information can be requested by extending the scope request. The audience is validated, we have two audiences, one for iOS and one for Android so the audience parameter to idToken.Validate(context, token, audience) isn’t used.

Google id token validator

The Google flow is a bit easier to set up since the same flow is used both for iOS and Android.

Conclusions & Tips

  • Be sure to allow CORS for https://appleid.apple.com in your backend.
  • See Flutter bloc authentication flow for a walkthrough of an authentication bloc flow in the app.
  • Decide on if you should threaten Google users as separate users from Email and password users or if they can log in through their Google account and/or password.
  • Make sure to test the flows on both iOS and Android devices and make sure to test different and weird scenarios.
  • Make sure to store the unique apple id of the users in your backend and that as an identifier for the user
  • Consider using different login paths in your backend, like POST /auth/apple, POST /auth/google, POST /auth/email but return the same response back from all of them so the code in the app can share the same logic. In our case, we return email, JWT access&refresh codes, and the authentication method.
  • Consider storing the last used authentication method in the app. In our app, we use it when the user logs out since we only want to pre-fill the email field only if the user did authenticate with email and password.

That’s it! 🚀

--

--

Erik
Dreamwod tech

Developer, backend, frontend, ML. Likes crossfit and training. Building on the app dreamwod.app.