Last active
October 7, 2024 06:50
-
-
Save aimore/bf72724fa6b41239a4278fdbf13d314f to your computer and use it in GitHub Desktop.
IdentityServer4 helper for swift 4
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
// | |
// IdentityServerHelper.swift | |
// Example | |
// | |
// Aimore Sa on 5/6/19. | |
// | |
import Foundation | |
import AppAuth | |
//protocol Auth { | |
// func getAuthToken(viewController: UIViewController) -> String | |
// func getUserInfo() -> UserInfo | |
// //TODO add clear Auth function | |
//} | |
struct AuthConstants{ | |
private(set) static var issuer: String = "https://demo.identityserver.io" | |
private(set) static var auhtClientId: String = "native.code" | |
private(set) static var redirectUri: String = "com.example.app:/oauth2redirect/example-provider" | |
private(set) static var appAuthExampleAuthStateKey: String = "authState"; | |
// static let redirectUri: String = "com.example.app:/oauth2redirect/example-provider" | |
} | |
struct UserInfo { | |
var userName: String? | |
var userSub: String? | |
var authToken: String? | |
var bearerToken: String? | |
} | |
class IdentityServerHelper : UIViewController { | |
typealias PostRegistrationCallback = (_ configuration: OIDServiceConfiguration?, _ registrationResponse: OIDRegistrationResponse?) -> Void | |
private var authState: OIDAuthState? | |
var accessToken : String = "" | |
private var user : UserInfo? | |
func getAuthToken(viewController: UIViewController) -> String { | |
guard let issuer = URL(string: AuthConstants.issuer) else { | |
print("Error creating URL for : \(AuthConstants.issuer)") | |
return "" | |
} | |
print("Fetching configuration for issuer: \(issuer)") | |
// discovers endpoints | |
OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { configuration, error in | |
guard let config = configuration else { | |
print("Error retrieving discovery document: \(error?.localizedDescription ?? "DEFAULT_ERROR")") | |
self.setAuthState(nil) | |
return | |
} | |
print("Got configuration: \(config)") | |
self.doAuthWithAutoCodeExchange(configuration: config, clientID: AuthConstants.auhtClientId, clientSecret: nil, controller: viewController) | |
} | |
return self.accessToken | |
} | |
func getUserInfo() -> UserInfo { | |
let user = UserInfo(userName: "zeca", userSub: "1202HS", authToken: "T3891414iqy4iqy4iq4yqi4ybj?", bearerToken: "kdfnkawgr3ety4q3j4gjqgf4jqv4jqgj4qg4jqg4j") | |
return user | |
} | |
//MARK get user info and bearer token | |
func userInfo() { | |
guard let userinfoEndpoint = self.authState?.lastAuthorizationResponse.request.configuration.discoveryDocument?.userinfoEndpoint else { | |
print("Userinfo endpoint not declared in discovery document") | |
return | |
} | |
print("Performing userinfo request") | |
let currentAccessToken: String? = self.authState?.lastTokenResponse?.accessToken | |
self.authState?.performAction() { (accessToken, idTOken, error) in | |
if error != nil { | |
print("Error fetching fresh tokens: \(error?.localizedDescription ?? "ERROR")") | |
return | |
} | |
guard let accessToken = accessToken else { | |
print("Error getting accessToken") | |
return | |
} | |
if currentAccessToken != accessToken { | |
print("Access token was refreshed automatically (\(currentAccessToken ?? "CURRENT_ACCESS_TOKEN") to \(accessToken))") | |
} else { | |
print("Access token was fresh and not updated \(accessToken)") | |
} | |
var urlRequest = URLRequest(url: userinfoEndpoint) | |
urlRequest.allHTTPHeaderFields = ["Authorization":"Bearer \(accessToken)"] | |
let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in | |
DispatchQueue.main.async { | |
guard error == nil else { | |
print("HTTP request failed \(error?.localizedDescription ?? "ERROR")") | |
return | |
} | |
guard let response = response as? HTTPURLResponse else { | |
print("Non-HTTP response") | |
return | |
} | |
guard let data = data else { | |
print("HTTP response data is empty") | |
return | |
} | |
var json: [AnyHashable: Any]? | |
do { | |
json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] | |
} catch { | |
print("JSON Serialization Error") | |
} | |
if response.statusCode != 200 { | |
// server replied with an error | |
let responseText: String? = String(data: data, encoding: String.Encoding.utf8) | |
if response.statusCode == 401 { | |
// "401 Unauthorized" generally indicates there is an issue with the authorization | |
// grant. Puts OIDAuthState into an error state. | |
let oauthError = OIDErrorUtilities.resourceServerAuthorizationError(withCode: 0, | |
errorResponse: json, | |
underlyingError: error) | |
self.authState?.update(withAuthorizationError: oauthError) | |
print("Authorization Error (\(oauthError)). Response: \(responseText ?? "RESPONSE_TEXT")") | |
} else { | |
print("HTTP: \(response.statusCode), Response: \(responseText ?? "RESPONSE_TEXT")") | |
} | |
return | |
} | |
if let json = json { | |
print("Success: \(json)") | |
} | |
} | |
} | |
task.resume() | |
} | |
} | |
//MARK manual AUth without code exchange | |
func doAuthWithoutCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?, viewController: UIViewController) { | |
guard let redirectURI = URL(string: kRedirectURI) else { | |
print("Error creating URL for : \(AuthConstants.redirectUri)") | |
return | |
} | |
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { | |
print("Error accessing AppDelegate") | |
return | |
} | |
// builds authentication request | |
let request = OIDAuthorizationRequest(configuration: configuration, | |
clientId: clientID, | |
clientSecret: clientSecret, | |
scopes: [OIDScopeOpenID, OIDScopeProfile], | |
redirectURL: redirectURI, | |
responseType: OIDResponseTypeCode, | |
additionalParameters: nil) | |
// performs authentication request | |
print("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")") | |
appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(request, presenting: viewController) { (response, error) in | |
if let response = response { | |
let authState = OIDAuthState(authorizationResponse: response) | |
self.setAuthState(authState) | |
print("Authorization response with code: \(response.authorizationCode ?? "DEFAULT_CODE")") | |
// could just call [self tokenExchange:nil] directly, but will let the user initiate it. | |
} else { | |
print("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") | |
} | |
} | |
} | |
//MARK Auto exchange token need to pass UIViewController as parameter! | |
func doAuthWithAutoCodeExchange(configuration: OIDServiceConfiguration, clientID: String, clientSecret: String?, controller: UIViewController) { | |
guard let redirectURI = URL(string: AuthConstants.redirectUri) else { | |
print("Error creating URL for : \(AuthConstants.redirectUri)") | |
return | |
} | |
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { | |
print("Error accessing AppDelegate") | |
return | |
} | |
// builds authentication request | |
let request = OIDAuthorizationRequest(configuration: configuration, | |
clientId: clientID, | |
clientSecret: clientSecret, | |
scopes: [OIDScopeOpenID, OIDScopeProfile], | |
redirectURL: redirectURI, | |
responseType: OIDResponseTypeCode, | |
additionalParameters: nil) | |
// performs authentication request | |
print("Initiating authorization request with scope: \(request.scope ?? "DEFAULT_SCOPE")") | |
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: controller) { | |
authState, error in | |
if let authState = authState { | |
self.setAuthState(authState) | |
print("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "DEFAULT_TOKEN")") | |
self.accessToken = authState.lastTokenResponse?.accessToken ?? "" | |
} else { | |
print("Authorization error: \(error?.localizedDescription ?? "DEFAULT_ERROR")") | |
self.setAuthState(nil) | |
} | |
} | |
} | |
//MARK init functions | |
func validateOAuthConfiguration() { | |
assert(AuthConstants.appAuthExampleAuthStateKey == "authState") | |
assert(AuthConstants.issuer == "https://demo.identityserver.io"); | |
assert(AuthConstants.auhtClientId == "native.code"); | |
assert(AuthConstants.redirectUri == "io.identityserver.demo:/oauthredirect"); | |
// verifies that the custom URI scheme has been updated in the Info.plist | |
guard let urlTypes: [AnyObject] = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [AnyObject], urlTypes.count > 0 else { | |
assertionFailure("No custom URI scheme has been configured for the project.") | |
return | |
} | |
guard let items = urlTypes[0] as? [String: AnyObject], | |
let urlSchemes = items["CFBundleURLSchemes"] as? [AnyObject], urlSchemes.count > 0 else { | |
assertionFailure("No custom URI scheme has been configured for the project.") | |
return | |
} | |
guard let urlScheme = urlSchemes[0] as? String else { | |
assertionFailure("No custom URI scheme has been configured for the project.") | |
return | |
} | |
assert(urlScheme != "com.example.app") | |
} | |
func loadState() { | |
guard let data = UserDefaults.standard.object(forKey: kAppAuthExampleAuthStateKey) as? Data else { | |
return | |
} | |
if let authState = NSKeyedUnarchiver.unarchiveObject(with: data) as? OIDAuthState { | |
self.setAuthState(authState) | |
} | |
} | |
func setAuthState(_ authState: OIDAuthState?) { | |
if (self.authState == authState) { | |
return; | |
} | |
self.authState = authState; | |
// self.authState?.stateChangeDelegate = (self as OIDAuthStateChangeDelegate); | |
self.stateChanged() | |
} | |
func saveState() { | |
var data: Data? = nil | |
if let authState = self.authState { | |
data = NSKeyedArchiver.archivedData(withRootObject: authState) | |
} | |
UserDefaults.standard.set(data, forKey: kAppAuthExampleAuthStateKey) | |
UserDefaults.standard.synchronize() | |
} | |
func stateChanged() { | |
self.saveState() | |
} | |
} | |
class test : UIViewController { | |
override func viewDidLoad() { | |
print("aaa") | |
} | |
} | |
//MARK: OIDAuthState Delegate | |
extension IdentityServerHelper : OIDAuthStateChangeDelegate, OIDAuthStateErrorDelegate { | |
func didChange(_ state: OIDAuthState) { | |
// self.stateChanged() | |
} | |
func authState(_ state: OIDAuthState, didEncounterAuthorizationError error: Error) { | |
print("Received authorization error: \(error)") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO: save tokens in Keychain