Skip to content

Commit

Permalink
Fix apple() provider and docs (#1113)
Browse files Browse the repository at this point in the history
  • Loading branch information
pilcrowonpaper authored Sep 14, 2023
1 parent ad11184 commit 6eda594
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 43 deletions.
6 changes: 6 additions & 0 deletions .auri/$ku1jk5lf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
package: "lucia" # package name
type: "minor" # "major", "minor", "patch"
---

Add `options.responseMode` params to `apple()`
2 changes: 1 addition & 1 deletion documentation/content/oauth/basics/oauth2.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ url.searchParams.set("response_mode", "query");

### Validate callback

Upon authentication, the provider will redirect the user back to your application. The url includes a code, and a state if the provider supports it. If a state is used, make sure to check if the state in the query params is the same as the one stored as a cookie.
Upon authentication, the provider will redirect the user back to your application (GET request). The url includes a code, and a state if the provider supports it. If a state is used, make sure to check if the state in the query params is the same as the one stored as a cookie.

Validate the code using `validateCallback()`. If the code is valid, this will return a new [`ProviderUserAuth`](/reference/oauth/interfaces#provideruserauth) among provider specific items (such as provider user data and access tokens). See [Handle users with OAuth](/oauth/basics/handle-users) for how to use it.

Expand Down
53 changes: 39 additions & 14 deletions documentation/content/oauth/providers/apple.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,24 @@ const apple: (
teamId: string;
keyId: string;
certificate: string;
scope: string[];
scope?: string[];
responseMode?: "query" | "form_post";
}
) => AppleProvider;
```

##### Parameters

| name | type | description | optional |
| -------------------- | ------------------------------------------ | -------------------------------------------------------------- | :------: |
| `auth` | [`Auth`](/reference/lucia/interfaces/auth) | Lucia instance | |
| `config.clientId` | `string` | Apple service identifier | |
| `config.redirectUri` | `string` | an authorized redirect URI | |
| `config.teamId` | `string` | Apple teamId | |
| `config.keyId ` | `string` | Apple private keyId | |
| `config.certificate` | `string` | p8 certificate as string [See how](#how-to-import-certificate) | |
| `config.scope` | `string[]` | an array of scopes ||
| name | type | description | default |
| --------------------- | ------------------------------------------ | --------------------------------------------------------------------- | --------- |
| `auth` | [`Auth`](/reference/lucia/interfaces/auth) | Lucia instance | |
| `config.clientId` | `string` | Apple service identifier | |
| `config.redirectUri` | `string` | an authorized redirect URI | |
| `config.teamId` | `string` | Apple teamId | |
| `config.keyId ` | `string` | Apple private keyId | |
| `config.certificate` | `string` | p8 certificate as string [See how](#how-to-import-certificate) | |
| `config.scope` | `string[]` | an array of scopes | `[]` |
| `config.responseMode` | `"query" \| "form_post"` | OIDC response mode - **must be `"form_post"` when requesting scopes** | `"query"` |

##### Returns

Expand Down Expand Up @@ -83,6 +85,33 @@ export const appleAuth = apple(auth, {
});
```

## Requesting scopes

When requesting scopes (`email` and `name`), the `options.responseMode` must be set to `"form_post"`. Unlike the default `"query"` response mode, \*\*Apple will send an `application/x-www-form-urlencoded` POST request. You can retrieve the code by parsing the search queries or the form data.

```ts
post("/login/apple/callback", async (request) => {
const url = new URL(request.url)
const code = url.searchParams.get("code");
if (!isValidState(request, code)) {
// ...
}
const appleUserAuth = await
// ...
})
```

Apple will also include a `user` field **only in the first response**, where you can access the user's name.

```ts
const url = new URL(request.url);
const userJSON = url.searchParams.get("user");
if (userJSON) {
const user = JSON.parse(userJSON);
const { firstName, lastName, email } = user;
}
```

## Interfaces

### `AppleAuth`
Expand Down Expand Up @@ -124,10 +153,6 @@ type AppleTokens = {
type AppleUser = {
email?: string;
email_verified?: boolean;
name?: {
firstName: string;
lastName: string;
};
sub: string;
};
```
Expand Down
2 changes: 1 addition & 1 deletion examples/tauri/github-oauth/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ <h2>Profile</h2>
</div>
<div>
<button id="login-button">Sign in with GitHub</button>
<button id="logout-button">Sign out</button>
<button id="logout-button">Sign out</button>
</div>
</div>
</body>
Expand Down
39 changes: 12 additions & 27 deletions packages/oauth/src/providers/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ type Config = {
teamId: string;
keyId: string;
certificate: string;
scope: string[];
responseMode?: "query" | "form_post";
scope?: string[];
};

const PROVIDER_ID = "apple";
Expand Down Expand Up @@ -51,14 +52,14 @@ export class AppleAuth<_Auth extends Auth = Auth> extends OAuth2ProviderAuth<
scope: scopeConfig
}
);
url.searchParams.set("response_mode", "query");
url.searchParams.set("response_mode", this.config.responseMode ?? "query");
return [url, state];
};

public validateCallback = async (
code: string
): Promise<AppleUserAuth<_Auth>> => {
const [appleTokens, userJSON] = await this.validateAuthorizationCode(code);
const appleTokens = await this.validateAuthorizationCode(code);
const idTokenPayload = decodeIdToken<{
sub: string;
email?: string;
Expand All @@ -67,15 +68,14 @@ export class AppleAuth<_Auth extends Auth = Auth> extends OAuth2ProviderAuth<
const appleUser: AppleUser = {
sub: idTokenPayload.sub,
email: idTokenPayload.email,
email_verified: idTokenPayload.email_verified,
name: userJSON.name
email_verified: idTokenPayload.email_verified
};
return new AppleUserAuth(this.auth, appleUser, appleTokens);
};

private validateAuthorizationCode = async (
code: string
): Promise<[tokens: AppleTokens, userJSON: AppleUserJSON]> => {
): Promise<AppleTokens> => {
const clientSecret = await createSecretId({
certificate: this.config.certificate,
teamId: this.config.teamId,
Expand All @@ -87,7 +87,6 @@ export class AppleAuth<_Auth extends Auth = Auth> extends OAuth2ProviderAuth<
refresh_token?: string;
expires_in: number;
id_token: string;
user: AppleUserJSON;
}>(code, "https://appleid.apple.com/auth/token", {
clientId: this.config.clientId,
redirectUri: this.config.redirectUri,
Expand All @@ -97,15 +96,12 @@ export class AppleAuth<_Auth extends Auth = Auth> extends OAuth2ProviderAuth<
}
});

return [
{
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token ?? null,
accessTokenExpiresIn: tokens.expires_in,
idToken: tokens.id_token
},
tokens.user
];
return {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token ?? null,
accessTokenExpiresIn: tokens.expires_in,
idToken: tokens.id_token
};
};
}

Expand Down Expand Up @@ -148,13 +144,6 @@ const createSecretId = async (config: {
return jwt;
};

type AppleUserJSON = {
name?: {
firstName: string;
lastName: string;
};
};

export type AppleTokens = {
accessToken: string;
refreshToken: string | null;
Expand All @@ -165,9 +154,5 @@ export type AppleTokens = {
export type AppleUser = {
email?: string;
email_verified?: boolean;
name?: {
firstName: string;
lastName: string;
};
sub: string;
};

0 comments on commit 6eda594

Please sign in to comment.