Skip to content

Instantly share code, notes, and snippets.

@aimore
Last active January 4, 2021 16:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aimore/bf72724fa6b41239a4278fdbf13d314f to your computer and use it in GitHub Desktop.
Save aimore/bf72724fa6b41239a4278fdbf13d314f to your computer and use it in GitHub Desktop.
IdentityServer4 helper for swift 4
//
// 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)")
}
}
@aimore
Copy link
Author

aimore commented Jun 7, 2019

TODO: save tokens in Keychain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment