Created
September 20, 2021 08:11
-
-
Save jonnyreeves/3a051cee26a403827de5c6278af6cf78 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import * as GoogleWebAuth from 'expo-auth-session/providers/google'; | |
import * as GoogleAppAuth from "expo-google-app-auth"; | |
import * as WebBrowser from 'expo-web-browser'; | |
import * as AppAuth from 'expo-app-auth'; | |
import { Platform } from 'react-native'; | |
import Constants, {AppOwnership} from 'expo-constants'; | |
// Call this function once in your Component which handles the Google authentication | |
// flow; typically done outside of the component decleration (ie: just after your | |
// import statements). | |
// Refer to the code example here: https://docs.expo.dev/guides/authentication/#google | |
export function maybeCompleteAuthSession() { | |
if (Platform.OS === 'web') { | |
WebBrowser.maybeCompleteAuthSession(); | |
} | |
} | |
// Initialises the state required to perform a Google Authentication request | |
// (authRequest, and authResult) and returns a func which will initiate the request | |
// across both Android and Web (promptAsync). | |
// Refer to the code example here: https://docs.expo.dev/guides/authentication/#google | |
export function useGoogleSignIn(authConfig) { | |
if (Platform.OS === 'web') { | |
const [ authRequet, authResult, promptAsync ] = GoogleWebAuth.useAuthRequest({ | |
webClientId: authConfig.webClientId, | |
scopes: authConfig.scopes | |
}); | |
return [ authRequet, authResult, promptAsync ]; | |
} else { | |
const [ authRequest, setAuthRequest ] = React.useState(true); | |
const [ authResult, setAuthResult ] = React.useState(null); | |
const promptAsync = () => { | |
setAuthRequest(false); | |
GoogleAppAuth.logInAsync(authConfig) | |
.then(authObject => { | |
setAuthRequest(true); | |
const type = authObject?.type; | |
if (type === 'cancel') { | |
setAuthResult({ type }); | |
} else if (type === 'success') { | |
setAuthResult({ | |
type, | |
authentication: authObject, | |
}); | |
} else { | |
setAuthResult(null); | |
} | |
}); | |
}; | |
return [ authRequest, authResult, promptAsync ]; | |
} | |
} | |
// Initialises the state required to perform a Google Refresh Token exchange | |
// (refreshRequest and refreshResult), and returns a func which will perform the | |
// refresh token exchange (refreshAsync). | |
export function useGoogleTokenRefresh(authConfig) { | |
const [ refreshRequest, setRefreshRequest ] = React.useState(true); | |
const [ refreshResult, setRefreshResult ] = React.useState(null); | |
const refreshAsync = (refreshToken) => { | |
if (!refreshToken) { | |
return setRefreshResult({ | |
type: 'cancelled' | |
}); | |
} | |
setRefreshRequest(false); | |
const clientId = getRefreshClientId(authConfig); | |
const config = { | |
issuer: 'https://accounts.google.com', | |
clientId, | |
scopes: authConfig.scopes, | |
}; | |
AppAuth.refreshAsync(config, refreshToken) | |
.then(res => { | |
setRefreshResult({ | |
type: 'success', | |
authentication: res | |
}) | |
}) | |
.catch(err => { | |
setRefreshResult({ | |
type: 'failed' | |
}); | |
}) | |
.finally(() => setRefreshRequest(true)) | |
}; | |
return [ refreshRequest, refreshResult, refreshAsync ]; | |
} | |
function getRefreshClientId(authConfig) { | |
const isStandalone = Constants.appOwnership === AppOwnership.Standalone; | |
const { androidStandaloneAppClientId, androidClientId } = authConfig; | |
if (Platform.OS === 'android') { | |
return (isStandalone) ? androidStandaloneAppClientId : androidClientId; | |
} | |
console.warn(`Could not determine refresh clientId for platform: ${Platform.OS}`) | |
return ""; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
As of September, 2021, the Expo Auth Session (expo-auth-session) provider for Google does not appear to support providing access to an OAuth Refresh Token (refreshToken). The legacy AppAuth (expo-app-auth) provider does, but does not work on the Web which slows app development.
This wrapper class presents a unified wrapper around both, providing a common interface based on React Hooks (following the interface of expo-auth-session).
Usage
The follow component will attempt to fetch a cached refresh token from secure storage and exchange it for a valid access token. If this exchange should fail (eg: no token in storage, or the refresh api call were to fail) the user will be prompted to authenticate via google. If the authentication completes successfully, and a refresh token is returned in the TokenResponse, this component will attempt to store the refresh token to skip future authentication prompts.