Skip to content

Commit

Permalink
merges with latest
Browse files Browse the repository at this point in the history
  • Loading branch information
rishabhpoddar committed Oct 4, 2023
2 parents ea76545 + b1c3f32 commit 94b5c65
Show file tree
Hide file tree
Showing 5 changed files with 611 additions and 14 deletions.
2 changes: 1 addition & 1 deletion v2/emailpassword/custom-ui/email-password-login.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/signup/email
</AppInfoForm>

The response body from the API call has a `status` property in it:
- `status: "OK"`: The response will also contain a `doesExist` boolean which will be `true` if the input email already belongs to an email password user.
- `status: "OK"`: The response will also contain a `exists` boolean which will be `true` if the input email already belongs to an email password user.
- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend.

</TabItem>
Expand Down
207 changes: 203 additions & 4 deletions v2/thirdparty/custom-ui/thirdparty-login.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ Coming Soon

<TabItem value="ios">

<h3>Step 1) Fetching the authorisation token on the frontend</h3>

For iOS you use the normal sign in with apple flow and then use the id token to login with SuperTokens

```swift
Expand Down Expand Up @@ -363,19 +365,31 @@ fileprivate class ViewController: UIViewController, ASAuthorizationControllerPre

<TabItem value="flutter">

<h3>Step 1) Fetching the authorisation token on the frontend</h3>

For flutter we use the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package, make sure to follow the prerequisites steps to get the package setup. After setup use the snippet below to trigger the apple sign in flow.

```dart
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
void loginWithApple() async {
try {
var credential = await SignInWithApple.getAppleIDCredential(scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
]);
var credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
// Required for Android only
webAuthenticationOptions: WebAuthenticationOptions(
clientId: "<CLIENT_ID>",
redirectUri: Uri.parse(
"<API_DOMAIN>/<API_BASE_PATH>/callback/apple",
),
),
);
String authorizationCode = credential.authorizationCode;
String? idToken = credential.identityToken;
String? email = credential.email;
String? firstname = credential.givenName;
String? lastName = credential.familyName;
Expand All @@ -387,6 +401,191 @@ void loginWithApple() async {
}
```

In the snippet above for Android we need to pass an additional `webAuthenticationOptions` property when signing in with Apple. This is because on Android the library uses the web login flow and we need to provide it with the client id and redirection uri. The `redirectUri` property here is the URL to which Apple will make a `POST` request after the user has logged in. The SuperTokens backend SDKs provide an API for this at `<API_DOMAIN>/<API_BASE_PATH>/callback/apple`.

<h3> Additional steps for Android </h3>

For android we also need to provide a way for the web login flow to redirect back to the app. By default the API provided by the backend SDKs redirect to the website domain you provide when initialising the SDK, we can override the API to have it redirect to our app instead. For example if you were using the Node.js SDK:

<BackendSDKTabs>

<TabItem value="nodejs">

```tsx
import ^{recipeNameCapitalLetters} from "supertokens-node/recipe/^{codeImportRecipeName}";

^{recipeNameCapitalLetters}.init({
^{nodeSignInUpFeatureStart}
// highlight-start
override: {
apis: (original) => {
return {
...original,
appleRedirectHandlerPOST: async (input) => {
if (original.appleRedirectHandlerPOST === undefined) {
throw Error("Should never come here");
}

// inut.formPostInfoFromProvider contains all the query params attached by Apple
const stateInBase64 = input.formPostInfoFromProvider.state;

// The web SDKs add a default state
if (stateInBase64 === undefined) {
// Redirect to android app
// We create a dummy URL to create the query string
const dummyUrl = new URL("http://localhost:8080");
for (const [key, value] of Object.entries(input.formPostInfoFromProvider)) {
dummyUrl.searchParams.set(key, `${value}`);
}

const queryString = dummyUrl.searchParams.toString();
// Refer to the README of sign_in_with_apple to understand what this url is
const redirectUrl = `intent://callback?${queryString}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end`;

input.options.res.setHeader("Location", redirectUrl, false);
input.options.res.setStatusCode(303);
input.options.res.sendHTMLResponse("");
} else {
// For the web flow we can use the original implementation
original.appleRedirectHandlerPOST(input);
}
},
};
},
},
// highlight-end
providers: [
// ...
],
^{nodeSignInUpFeatureEnd}
})
```

</TabItem>

<TabItem value="go">

```go
import (
"net/http"
"strings"

"github.com/supertokens/supertokens-golang/recipe/^{codeImportRecipeName}"
^{goTPModelsImport}
"github.com/supertokens/supertokens-golang/recipe/^{codeImportRecipeName}/^{goModelName}"
)

func main() {
^{codeImportRecipeName}.Init(^{goModelNameForInit}.TypeInput{
Override: &^{goModelName}.OverrideStruct{
// highlight-start
APIs: func(originalImplementation ^{goModelName}.APIInterface) ^{goModelName}.APIInterface {
originalAppleRedirectPost := *originalImplementation.AppleRedirectHandlerPOST

*originalImplementation.AppleRedirectHandlerPOST = func(formPostInfoFromProvider map[string]interface{}, options tpmodels.APIOptions, userContext *map[string]interface{}) error {
// formPostInfoFromProvider contains all the query params attached by Apple
state, stateOk := formPostInfoFromProvider["state"].(string)

queryParams := []string{}
if (!stateOk) || state == "" {
// Redirect to android app
for key, value := range formPostInfoFromProvider {
queryParams = append(queryParams, key+"="+value.(string))
}

queryString := ""
if len(queryParams) > 0 {
queryString = strings.Join(queryParams, "&")
}

// Refer to the README of sign_in_with_apple to understand what this url is
redirectUri := "intent://callback?" + queryString + "#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end"

options.Res.Header().Set("Location", redirectUri)
options.Res.WriteHeader(http.StatusSeeOther)
return nil
} else {
return originalAppleRedirectPost(formPostInfoFromProvider, options, userContext)
}
}

return originalImplementation
},
},
// highlight-end
^{goSignInUpFeatureStart}
Providers: []tpmodels.ProviderInput{
// ...
},
^{goSignInUpFeatureEnd}
})
}
```

</TabItem>

<TabItem value="python">

```python
from supertokens_python.recipe import ^{codeImportRecipeName}
from supertokens_python.recipe.^{codeImportRecipeName}.interfaces import APIInterface, APIOptions
from typing import Union, Dict, Any
from supertokens_python.recipe.thirdparty.provider import Provider, RedirectUriInfo

# highlight-start
def override_thirdparty_apis(original_implementation: APIInterface):
original_apple_redirect_post = original_implementation.apple_redirect_handler_post

async def apple_redirect_handler_post(
form_post_info: Dict[str, Any],
api_options: APIOptions,
user_context: Dict[str, Any]
):
# form_post_info contains all the query params attached by Apple
state = form_post_info["state"]

# The web SDKs add a default state
if state is None:
query_items = []

for key, value in form_post_info.items():
query_items.append(f"{key}={value}")

query_string = "&".join(query_items)

# Refer to the README of sign_in_with_apple to understand what this url is
redirect_url = f"intent://callback?${query_string}#Intent;package=YOUR.PACKAGE.IDENTIFIER;scheme=signinwithapple;end"

api_options.response.set_header("Location", redirect_url)
api_options.response.set_status_code(303)
api_options.response.set_html_content("")
else:
return original_apple_redirect_post(form_post_info, api_options, user_context)

original_implementation.apple_redirect_handler_post = apple_redirect_handler_post
return original_implementation
# highlight-end

^{codeImportRecipeName}.init(
# highlight-start
override=^{codeImportRecipeName}.InputOverrideConfig(
apis=override_thirdparty_apis
),
# highlight-end
^{pythonSignInUpFeatureStart}
providers=[
# ...
]
^{pythonSignInUpFeatureEnd}
)
```

</TabItem>

</BackendSDKTabs>

In the code above we override the `appleRedirectHandlerPOST` API to check if the request was made by our Android app (You can skip checking the state if you only have a mobile app and no website). `sign_in_with_apple` requires us to parse the query params sent by apple and include them in the redirect URL in a specific way, and then we simply redirect to the deep link url. Refer to the README for `sign_in_with_apple` to read about the deep link setup required in Android.

</TabItem>

</FrontendMobileSubTabs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ curl --location --request GET '^{form_apiDomain}^{form_apiBasePath}/signup/email
</AppInfoForm>

The response body from the API call has a `status` property in it:
- `status: "OK"`: The response will also contain a `doesExist` boolean which will be `true` if the input email already belongs to an email password user.
- `status: "OK"`: The response will also contain a `exists` boolean which will be `true` if the input email already belongs to an email password user.
- `status: "GENERAL_ERROR"`: This is only possible if you have overriden the backend API to send back a custom error message which should be displayed on the frontend.

</TabItem>
Expand Down
Loading

0 comments on commit 94b5c65

Please sign in to comment.