forked from Accedia/appleauth-net
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AppleAuthProvider.cs
175 lines (151 loc) · 8.53 KB
/
AppleAuthProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
using AppleAuth.Constants;
using AppleAuth.Cryptography;
using AppleAuth.Exceptions;
using AppleAuth.Http;
using AppleAuth.TokenObjects;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace AppleAuth
{
/// <summary>
///<c>AppleAuthProvider</c> contains methods for retrieving an authorization token from Apple,
///refreshing an existing token, generating `href` attribute for Sign In With Apple button
/// </summary>
/// <remarks>
/// The tokens returned from Apple authorization servers are short lived and should be used for creating
/// sessions or accounts in your system.
/// </remarks>
public class AppleAuthProvider
{
private static string ClientID { get; set; }
private static string TeamID { get; set; }
private static string KeyID { get; set; }
private static string RedirectURL { get; set; }
private string State { get; set; }
private int ExpirationInMinutes { get; set; }
private static readonly AppleRestClient _appleRestClient = new AppleRestClient();
private readonly TokenGenerator _tokenGenerator = new TokenGenerator();
/// <summary>Constructor which initializes new instance of AppleAuthProvider with parameters used in the requests to Apple</summary>
/// <param name="clientId">Identifier that is set and obtained from your developer account. (aka "Service ID" that is configured for “Sign In with Apple”)</param>
/// <param name="teamId">A 10-character key identifier obtained from your developer account.</param>
/// <param name="keyId">A 10-character key identifier obtained from your developer account. Configured for "Sign In with Apple" </param>
/// <param name="redirectUrl">URL to which the user will be redirected after successful verification.
/// You need to configure a verified domain and map the redirect URL to it. Can’t be an IP address or localhost </param>
/// <param name="state">Can be used for any internal identifiers (e.g. Session IDs, User IDs, Query Strings, etc.)</param>
/// <param name="expiration">Can be used to add an expiration for the client secret when generated. Defaults to 5 if not specified.</param>
public AppleAuthProvider(string clientId, string teamId, string keyId, string redirectUrl, string state, int expiration = 5)
{
ClientID = clientId;
TeamID = teamId;
KeyID = keyId;
RedirectURL = redirectUrl;
State = state;
ExpirationInMinutes = expiration;
}
/// <summary>
/// Retrieves an <c>AuthorizationToken</c> object from Apple. Use this object to create users or sessions.
/// </summary>
/// <exception cref="AppleRequestException">
/// Thrown when HTTP response from Apple is different than 200 OK.
/// </exception>
/// <returns>
/// AuthorizationToken object
/// </returns>
/// <params>
/// <param name="authorizationCode">Received from Apple after successfully redirecting the user</param>
/// <param name="privateKey">Content of the .p8 key file.</param>
/// </params>
/// <remarks>
/// For more information about Apple's error responses visit: https://developer.apple.com/documentation/sign_in_with_apple/errorresponse
/// </remarks>
public async Task<AuthorizationToken> GetAuthorizationToken(string authorizationCode, string privateKey)
{
ValidateStringParameters(new List<string> { authorizationCode, privateKey });
string clientSecret = _tokenGenerator.GenerateAppleClientSecret(privateKey, TeamID, ClientID, KeyID, ExpirationInMinutes);
HttpRequestMessage request = _appleRestClient.GenerateRequestMessage(TokenType.AuthorizationCode, authorizationCode, clientSecret, ClientID, RedirectURL);
string response = await _appleRestClient.SentTokenRequest(request);
var tokenResponse = JsonSerializer.Deserialize<AuthorizationToken>(response);
TokenGenerator.VerifyAppleIDToken(tokenResponse.Token, ClientID);
SetUserInformation(tokenResponse);
return tokenResponse;
}
/// <summary>
/// Verifies if a token is valid.
/// Use this method to check daily if the user is still signed in on your app using Apple ID.
/// </summary>
/// <exception cref="AppleRequestException">
/// Thrown when HTTP response from Apple is different than 200 OK.
/// </exception>
///<remarks>
/// For reference of Apple's error responses visit: https://developer.apple.com/documentation/sign_in_with_apple/errorresponse
/// </remarks>
/// <param name="refreshToken">Received from Apple when successfully retrieving an AuthorizationToken object</param>
/// <param name="privateKey">Content of the .p8 key file.</param>
/// <returns>
/// AuthorizationToken object containing only AuthorizationCode and Expiration
/// </returns>
public async Task<AuthorizationToken> GetRefreshToken(string refreshToken, string privateKey)
{
ValidateStringParameters(new List<string> { refreshToken, privateKey });
string clientSecret = _tokenGenerator.GenerateAppleClientSecret(privateKey, TeamID, ClientID, KeyID);
HttpRequestMessage request = _appleRestClient.GenerateRequestMessage(TokenType.RefreshToken, refreshToken, clientSecret, ClientID, RedirectURL);
string response = await _appleRestClient.SentTokenRequest(request);
var refreshTokenObject = JsonSerializer.Deserialize<AuthorizationToken>(response);
return refreshTokenObject;
}
/// <summary>
/// Generates url for the 'href' attribute of the "Sign in with Apple" button
/// </summary>
/// <remarks>
/// For information how to display the "Sign in with Apple" buttons visit: https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/displaying_sign_in_with_apple_buttons
/// </remarks>
/// <returns>string/url</returns>
public string GetButtonHref()
{
var baseUrl = GlobalConstants.AppleAuthorizeURL;
var requestParams = $"?client_id={ClientID}&scope=name email&redirect_uri={RedirectURL}&state={State}&response_type=code id_token&response_mode=form_post&usePopup=true";
return baseUrl + requestParams;
}
/// <summary>
/// Reads the JSON Web Token returned from apple
/// and creates new UserInformation object with information for the specific user
/// </summary>
/// <param name="tokenResponse">AuthorizationToken object</param>
private void SetUserInformation(AuthorizationToken tokenResponse)
{
var tokenHandler = new JwtSecurityTokenHandler();
var deserializeUserInformation = tokenHandler.ReadJwtToken(tokenResponse.Token);
if (deserializeUserInformation != null && deserializeUserInformation.Claims.Any())
{
var claims = deserializeUserInformation.Claims;
var email = claims.FirstOrDefault(x => x.Type == ClaimConstants.Email);
var email_verified = claims.FirstOrDefault(x => x.Type == ClaimConstants.EmailVerified);
var sub = claims.FirstOrDefault(x => x.Type == ClaimConstants.Sub);
var auth_time = claims.FirstOrDefault(x => x.Type == ClaimConstants.AuthenticationTime).Value;
var timeOfAuthentication = DateTimeOffset.FromUnixTimeSeconds(long.Parse(auth_time)).DateTime;
tokenResponse.UserInformation = new UserInformation
{
Email = email != null ? email.Value : string.Empty,
EmailVerified = email_verified != null ? email_verified.Value : "False",
UserID = sub != null ? sub.Value : string.Empty,
TimeOfAuthentication = timeOfAuthentication
};
}
}
/// <summary>
/// If any parameter is null or empty string throws an exception
/// </summary>
private void ValidateStringParameters(List<string> parameters)
{
if (parameters.Any(p => string.IsNullOrEmpty(p)))
{
throw new InvalidParametersException("One or more parameters is null");
}
}
}
}